diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs index f8991d4eeb..02000019c4 100644 --- a/crates/ruff_formatter/src/builders.rs +++ b/crates/ruff_formatter/src/builders.rs @@ -1,14 +1,13 @@ use crate::format_element::tag::{Condition, Tag}; use crate::prelude::tag::{DedentMode, GroupMode, LabelId}; use crate::prelude::*; -use crate::{format_element, write, Argument, Arguments, GroupId, TextSize}; +use crate::{format_element, write, Argument, Arguments, FormatContext, GroupId, TextSize}; use crate::{Buffer, VecBuffer}; use ruff_text_size::TextRange; use std::cell::Cell; use std::marker::PhantomData; use std::num::NonZeroU8; -use std::rc::Rc; use Tag::*; /// A line break that only gets printed if the enclosing `Group` doesn't fit on a single line. @@ -361,31 +360,65 @@ impl std::fmt::Debug for DynamicText<'_> { } } -/// Creates a text from a dynamic string and a range of the input source -pub fn static_text_slice(text: Rc, range: TextRange) -> StaticTextSlice { - debug_assert_no_newlines(&text[range]); - - StaticTextSlice { text, range } -} - -#[derive(Eq, PartialEq)] -pub struct StaticTextSlice { - text: Rc, +/// Emits a text as it is written in the source document. Optimized to avoid allocations. +pub const fn source_text_slice( range: TextRange, -} - -impl Format for StaticTextSlice { - fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - f.write_element(FormatElement::StaticTextSlice { - text: self.text.clone(), - range: self.range, - }) + newlines: ContainsNewlines, +) -> SourceTextSliceBuilder { + SourceTextSliceBuilder { + range, + new_lines: newlines, } } -impl std::fmt::Debug for StaticTextSlice { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::write!(f, "StaticTextSlice({})", &self.text[self.range]) +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ContainsNewlines { + /// The string contains newline characters + Yes, + /// The string contains no newline characters + No, + + /// The string may contain newline characters, search the string to determine if there are any newlines. + Detect, +} + +#[derive(Eq, PartialEq, Debug)] +pub struct SourceTextSliceBuilder { + range: TextRange, + new_lines: ContainsNewlines, +} + +impl Format for SourceTextSliceBuilder +where + Context: FormatContext, +{ + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let source_code = f.context().source_code(); + let slice = source_code.slice(self.range); + debug_assert_no_newlines(slice.text(source_code)); + + let contains_newlines = match self.new_lines { + ContainsNewlines::Yes => { + debug_assert!( + slice.text(source_code).contains('\n'), + "Text contains no new line characters but the caller specified that it does." + ); + true + } + ContainsNewlines::No => { + debug_assert!( + !slice.text(source_code).contains('\n'), + "Text contains new line characters but the caller specified that it does not." + ); + false + } + ContainsNewlines::Detect => slice.text(source_code).contains('\n'), + }; + + f.write_element(FormatElement::SourceCodeSlice { + slice, + contains_newlines, + }) } } diff --git a/crates/ruff_formatter/src/format_element.rs b/crates/ruff_formatter/src/format_element.rs index e43fbdbdc8..a7dd7dedae 100644 --- a/crates/ruff_formatter/src/format_element.rs +++ b/crates/ruff_formatter/src/format_element.rs @@ -7,8 +7,9 @@ use std::ops::Deref; use std::rc::Rc; use crate::format_element::tag::{LabelId, Tag}; +use crate::source_code::SourceCodeSlice; use crate::TagKind; -use ruff_text_size::{TextRange, TextSize}; +use ruff_text_size::TextSize; /// Language agnostic IR for formatting source code. /// @@ -39,8 +40,12 @@ pub enum FormatElement { text: Box, }, - /// Token constructed by slicing a defined range from a static string. - StaticTextSlice { text: Rc, range: TextRange }, + /// Text that gets emitted as it is in the source code. Optimized to avoid any allocations. + SourceCodeSlice { + slice: SourceCodeSlice, + /// Whether the string contains any new line characters + contains_newlines: bool, + }, /// Prevents that line suffixes move past this boundary. Forces the printer to print any pending /// line suffixes, potentially by inserting a hard line break. @@ -70,9 +75,14 @@ impl std::fmt::Debug for FormatElement { FormatElement::DynamicText { text, .. } => { fmt.debug_tuple("DynamicText").field(text).finish() } - FormatElement::StaticTextSlice { text, .. } => { - fmt.debug_tuple("Text").field(text).finish() - } + FormatElement::SourceCodeSlice { + slice, + contains_newlines, + } => fmt + .debug_tuple("Text") + .field(slice) + .field(contains_newlines) + .finish(), FormatElement::LineSuffixBoundary => write!(fmt, "LineSuffixBoundary"), FormatElement::BestFitting(best_fitting) => { fmt.debug_tuple("BestFitting").field(&best_fitting).finish() @@ -221,7 +231,7 @@ impl FormatElement { pub const fn is_text(&self) -> bool { matches!( self, - FormatElement::StaticTextSlice { .. } + FormatElement::SourceCodeSlice { .. } | FormatElement::DynamicText { .. } | FormatElement::StaticText { .. } ) @@ -240,7 +250,9 @@ impl FormatElements for FormatElement { FormatElement::Line(line_mode) => matches!(line_mode, LineMode::Hard | LineMode::Empty), FormatElement::StaticText { text } => text.contains('\n'), FormatElement::DynamicText { text, .. } => text.contains('\n'), - FormatElement::StaticTextSlice { text, range } => text[*range].contains('\n'), + FormatElement::SourceCodeSlice { + contains_newlines, .. + } => *contains_newlines, FormatElement::Interned(interned) => interned.will_break(), // Traverse into the most flat version because the content is guaranteed to expand when even // the most flat version contains some content that forces a break. @@ -380,20 +392,19 @@ mod sizes { // be recomputed at a later point in time? // You reduced the size of a format element? Excellent work! - use crate::format_element::BestFitting; - use crate::prelude::tag::VerbatimKind; - use crate::prelude::Interned; - use ruff_text_size::TextRange; use static_assertions::assert_eq_size; - assert_eq_size!(TextRange, [u8; 8]); - assert_eq_size!(VerbatimKind, [u8; 8]); - assert_eq_size!(Interned, [u8; 16]); - assert_eq_size!(BestFitting, [u8; 16]); + assert_eq_size!(ruff_text_size::TextRange, [u8; 8]); + assert_eq_size!(crate::prelude::tag::VerbatimKind, [u8; 8]); + assert_eq_size!(crate::prelude::Interned, [u8; 16]); + assert_eq_size!(crate::format_element::BestFitting, [u8; 16]); + + #[cfg(not(debug_assertions))] + assert_eq_size!(crate::SourceCodeSlice, [u8; 8]); #[cfg(not(debug_assertions))] assert_eq_size!(crate::format_element::Tag, [u8; 16]); #[cfg(not(debug_assertions))] - assert_eq_size!(crate::FormatElement, [u8; 32]); + assert_eq_size!(crate::FormatElement, [u8; 24]); } diff --git a/crates/ruff_formatter/src/format_element/document.rs b/crates/ruff_formatter/src/format_element/document.rs index d10644bb31..487c772a1d 100644 --- a/crates/ruff_formatter/src/format_element/document.rs +++ b/crates/ruff_formatter/src/format_element/document.rs @@ -3,6 +3,7 @@ use crate::format_element::tag::DedentMode; use crate::prelude::tag::GroupMode; use crate::prelude::*; use crate::printer::LineEnding; +use crate::source_code::SourceCode; use crate::{format, write}; use crate::{ BufferExtensions, Format, FormatContext, FormatElement, FormatOptions, FormatResult, Formatter, @@ -80,7 +81,9 @@ impl Document { } FormatElement::StaticText { text } => text.contains('\n'), FormatElement::DynamicText { text, .. } => text.contains('\n'), - FormatElement::StaticTextSlice { text, range } => text[*range].contains('\n'), + FormatElement::SourceCodeSlice { + contains_newlines, .. + } => *contains_newlines, FormatElement::ExpandParent | FormatElement::Line(LineMode::Hard | LineMode::Empty) => true, _ => false, @@ -99,6 +102,13 @@ impl Document { let mut interned: FxHashMap<&Interned, bool> = FxHashMap::default(); propagate_expands(self, &mut enclosing, &mut interned); } + + pub fn display<'a>(&'a self, source_code: SourceCode<'a>) -> DisplayDocument { + DisplayDocument { + elements: self.elements.as_slice(), + source_code, + } + } } impl From> for Document { @@ -115,9 +125,14 @@ impl Deref for Document { } } -impl std::fmt::Display for Document { +pub struct DisplayDocument<'a> { + elements: &'a [FormatElement], + source_code: SourceCode<'a>, +} + +impl std::fmt::Display for DisplayDocument<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let formatted = format!(IrFormatContext::default(), [self.elements.as_slice()]) + let formatted = format!(IrFormatContext::new(self.source_code), [self.elements]) .expect("Formatting not to throw any FormatErrors"); f.write_str( @@ -129,18 +144,33 @@ impl std::fmt::Display for Document { } } -#[derive(Clone, Default, Debug)] -struct IrFormatContext { +#[derive(Clone, Debug)] +struct IrFormatContext<'a> { /// The interned elements that have been printed to this point printed_interned_elements: HashMap, + + source_code: SourceCode<'a>, } -impl FormatContext for IrFormatContext { +impl<'a> IrFormatContext<'a> { + fn new(source_code: SourceCode<'a>) -> Self { + Self { + source_code, + printed_interned_elements: HashMap::new(), + } + } +} + +impl FormatContext for IrFormatContext<'_> { type Options = IrFormatOptions; fn options(&self) -> &Self::Options { &IrFormatOptions } + + fn source_code(&self) -> SourceCode { + self.source_code + } } #[derive(Debug, Clone, Default)] @@ -165,7 +195,7 @@ impl FormatOptions for IrFormatOptions { } } -impl Format for &[FormatElement] { +impl Format> for &[FormatElement] { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { use Tag::*; @@ -189,7 +219,7 @@ impl Format for &[FormatElement] { element @ FormatElement::Space | element @ FormatElement::StaticText { .. } | element @ FormatElement::DynamicText { .. } - | element @ FormatElement::StaticTextSlice { .. } => { + | element @ FormatElement::SourceCodeSlice { .. } => { if !in_text { write!(f, [text("\"")])?; } @@ -489,7 +519,7 @@ impl Format for &[FormatElement] { struct ContentArrayStart; -impl Format for ContentArrayStart { +impl Format> for ContentArrayStart { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { use Tag::*; @@ -505,7 +535,7 @@ impl Format for ContentArrayStart { struct ContentArrayEnd; -impl Format for ContentArrayEnd { +impl Format> for ContentArrayEnd { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { use Tag::*; f.write_elements([ @@ -615,8 +645,9 @@ impl FormatElements for [FormatElement] { #[cfg(test)] mod tests { use crate::prelude::*; - use crate::SimpleFormatContext; use crate::{format, format_args, write}; + use crate::{SimpleFormatContext, SourceCode}; + use ruff_text_size::{TextRange, TextSize}; #[test] fn display_elements() { @@ -641,7 +672,51 @@ mod tests { let document = formatted.into_document(); assert_eq!( - &std::format!("{document}"), + &std::format!("{}", document.display(SourceCode::default())), + r#"[ + group([ + "(", + indent([ + soft_line_break, + "Some longer content That should ultimately break" + ]), + soft_line_break + ]) +]"# + ); + } + + #[test] + fn display_elements_with_source_text_slice() { + let source_code = "Some longer content\nThat should ultimately break"; + let formatted = format!( + SimpleFormatContext::default().with_source_code(source_code), + [format_with(|f| { + write!( + f, + [group(&format_args![ + text("("), + soft_block_indent(&format_args![ + source_text_slice( + TextRange::at(TextSize::new(0), TextSize::new(19)), + ContainsNewlines::No + ), + space(), + source_text_slice( + TextRange::at(TextSize::new(20), TextSize::new(28)), + ContainsNewlines::No + ), + ]) + ])] + ) + })] + ) + .unwrap(); + + let document = formatted.into_document(); + + assert_eq!( + &std::format!("{}", document.display(SourceCode::new(source_code))), r#"[ group([ "(", @@ -677,7 +752,7 @@ mod tests { ]); assert_eq!( - &std::format!("{document}"), + &std::format!("{}", document.display(SourceCode::default())), r#"[ "[", group([ diff --git a/crates/ruff_formatter/src/lib.rs b/crates/ruff_formatter/src/lib.rs index d322645f97..5498d03342 100644 --- a/crates/ruff_formatter/src/lib.rs +++ b/crates/ruff_formatter/src/lib.rs @@ -33,6 +33,7 @@ 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; @@ -47,6 +48,7 @@ pub use buffer::{ VecBuffer, }; pub use builders::BestFitting; +pub use source_code::{SourceCode, SourceCodeSlice}; pub use crate::diagnostics::{ActualStart, FormatError, InvalidDocumentError, PrintError}; pub use format_element::{normalize_newlines, FormatElement, LINE_TERMINATORS}; @@ -202,6 +204,9 @@ pub trait FormatContext { /// Returns the formatting options fn options(&self) -> &Self::Options; + + /// Returns the source code from the document that gets formatted. + fn source_code(&self) -> SourceCode; } /// Options customizing how the source code should be formatted. @@ -219,11 +224,20 @@ pub trait FormatOptions { #[derive(Debug, Default, Eq, PartialEq)] pub struct SimpleFormatContext { options: SimpleFormatOptions, + source_code: String, } impl SimpleFormatContext { pub fn new(options: SimpleFormatOptions) -> Self { - Self { options } + Self { + options, + source_code: String::new(), + } + } + + pub fn with_source_code(mut self, code: &str) -> Self { + self.source_code = String::from(code); + self } } @@ -233,9 +247,13 @@ impl FormatContext for SimpleFormatContext { fn options(&self) -> &Self::Options { &self.options } + + fn source_code(&self) -> SourceCode { + SourceCode::new(&self.source_code) + } } -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Debug, Default, Eq, PartialEq, Clone)] pub struct SimpleFormatOptions { pub indent_style: IndentStyle, pub line_width: LineWidth, @@ -302,15 +320,18 @@ where Context: FormatContext, { pub fn print(&self) -> PrintResult { + let source_code = self.context.source_code(); let print_options = self.context.options().as_print_options(); - let printed = Printer::new(print_options).print(&self.document)?; + let printed = Printer::new(source_code, print_options).print(&self.document)?; Ok(printed) } pub fn print_with_indent(&self, indent: u16) -> PrintResult { + let source_code = self.context.source_code(); let print_options = self.context.options().as_print_options(); - let printed = Printer::new(print_options).print_with_indent(&self.document, indent)?; + let printed = + Printer::new(source_code, print_options).print_with_indent(&self.document, indent)?; Ok(printed) } diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index 4be2d274ed..5d5c171b48 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -23,6 +23,7 @@ use crate::printer::line_suffixes::{LineSuffixEntry, LineSuffixes}; use crate::printer::queue::{ AllPredicate, FitsEndPredicate, FitsQueue, PrintQueue, Queue, SingleEntryPredicate, }; +use crate::source_code::SourceCode; use drop_bomb::DebugDropBomb; use ruff_text_size::{TextLen, TextSize}; use std::num::NonZeroU8; @@ -32,12 +33,14 @@ use unicode_width::UnicodeWidthChar; #[derive(Debug, Default)] pub struct Printer<'a> { options: PrinterOptions, + source_code: SourceCode<'a>, state: PrinterState<'a>, } impl<'a> Printer<'a> { - pub fn new(options: PrinterOptions) -> Self { + pub fn new(source_code: SourceCode<'a>, options: PrinterOptions) -> Self { Self { + source_code, options, state: PrinterState::default(), } @@ -96,7 +99,10 @@ impl<'a> Printer<'a> { FormatElement::StaticText { text } => self.print_text(text, None), FormatElement::DynamicText { text } => self.print_text(text, None), - FormatElement::StaticTextSlice { text, range } => self.print_text(&text[*range], None), + FormatElement::SourceCodeSlice { slice, .. } => { + let text = slice.text(self.source_code); + self.print_text(text, Some(slice.range())) + } FormatElement::Line(line_mode) => { if args.mode().is_flat() && matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace) @@ -994,8 +1000,9 @@ impl<'a, 'print> FitsMeasurer<'a, 'print> { FormatElement::StaticText { text } => return Ok(self.fits_text(text)), FormatElement::DynamicText { text, .. } => return Ok(self.fits_text(text)), - FormatElement::StaticTextSlice { text, range } => { - return Ok(self.fits_text(&text[*range])) + FormatElement::SourceCodeSlice { slice, .. } => { + let text = slice.text(self.printer.source_code); + return Ok(self.fits_text(text)); } FormatElement::LineSuffixBoundary => { if self.state.has_line_suffix { @@ -1256,6 +1263,7 @@ struct FitsState { mod tests { use crate::prelude::*; use crate::printer::{LineEnding, PrintWidth, Printer, PrinterOptions}; + use crate::source_code::SourceCode; use crate::{format_args, write, Document, FormatState, IndentStyle, Printed, VecBuffer}; fn format(root: &dyn Format) -> Printed { @@ -1274,7 +1282,7 @@ mod tests { ) -> Printed { let formatted = crate::format!(SimpleFormatContext::default(), [root]).unwrap(); - Printer::new(options) + Printer::new(SourceCode::default(), options) .print(formatted.document()) .expect("Document to be valid") } @@ -1510,9 +1518,12 @@ two lines`, let document = Document::from(buffer.into_vec()); - let printed = Printer::new(PrinterOptions::default().with_print_width(PrintWidth::new(10))) - .print(&document) - .unwrap(); + let printed = Printer::new( + SourceCode::default(), + PrinterOptions::default().with_print_width(PrintWidth::new(10)), + ) + .print(&document) + .unwrap(); assert_eq!( printed.as_code(), diff --git a/crates/ruff_formatter/src/source_code.rs b/crates/ruff_formatter/src/source_code.rs new file mode 100644 index 0000000000..77bf35fab5 --- /dev/null +++ b/crates/ruff_formatter/src/source_code.rs @@ -0,0 +1,81 @@ +use ruff_text_size::TextRange; +use std::fmt::{Debug, Formatter}; + +/// The source code of a document that gets formatted +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default)] +pub struct SourceCode<'a> { + text: &'a str, +} + +impl<'a> SourceCode<'a> { + pub fn new(text: &'a str) -> Self { + Self { text } + } + + pub fn slice(self, range: TextRange) -> SourceCodeSlice { + assert!( + usize::from(range.end()) <= self.text.len(), + "Range end {:?} out of bounds {}.", + range.end(), + self.text.len() + ); + + assert!( + self.text.is_char_boundary(usize::from(range.start())), + "The range start position {:?} is not a char boundary.", + range.start() + ); + + assert!( + self.text.is_char_boundary(usize::from(range.end())), + "The range end position {:?} is not a char boundary.", + range.end() + ); + + SourceCodeSlice { + range, + #[cfg(debug_assertions)] + text: String::from(&self.text[range]).into_boxed_str(), + } + } +} + +impl Debug for SourceCode<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SourceCode").field(&self.text).finish() + } +} + +/// A slice into the source text of a document. +/// +/// It only stores the range in production builds for a more compact representation, but it +/// keeps the original text in debug builds for better developer experience. +#[derive(Clone, Eq, PartialEq)] +pub struct SourceCodeSlice { + range: TextRange, + #[cfg(debug_assertions)] + text: Box, +} + +impl SourceCodeSlice { + /// Returns the slice's text. + pub fn text<'a>(&self, code: SourceCode<'a>) -> &'a str { + assert!(usize::from(self.range.end()) <= code.text.len(), "The range of this slice is out of bounds. Did you provide the correct source code for this slice?"); + &code.text[self.range] + } + + pub fn range(&self) -> TextRange { + self.range + } +} + +impl Debug for SourceCodeSlice { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let mut tuple = f.debug_tuple("SourceCodeSlice"); + + #[cfg(debug_assertions)] + tuple.field(&self.text); + + tuple.field(&self.range).finish() + } +} diff --git a/crates/ruff_python_formatter/src/context.rs b/crates/ruff_python_formatter/src/context.rs index 36827dd6de..301dbff052 100644 --- a/crates/ruff_python_formatter/src/context.rs +++ b/crates/ruff_python_formatter/src/context.rs @@ -1,36 +1,34 @@ -use std::rc::Rc; - -use ruff_formatter::{FormatContext, SimpleFormatOptions}; +use ruff_formatter::{FormatContext, SimpleFormatOptions, SourceCode}; use ruff_python_ast::source_code::Locator; -pub struct ASTFormatContext { +#[derive(Clone, Debug)] +pub struct ASTFormatContext<'source> { options: SimpleFormatOptions, - contents: Rc, + contents: &'source str, } -impl ASTFormatContext { - pub fn new(options: SimpleFormatOptions, contents: &str) -> Self { - Self { - options, - contents: Rc::from(contents), - } +impl<'source> ASTFormatContext<'source> { + pub fn new(options: SimpleFormatOptions, contents: &'source str) -> Self { + Self { options, contents } + } + + pub fn contents(&self) -> &'source str { + self.contents + } + + pub fn locator(&self) -> Locator<'source> { + Locator::new(self.contents) } } -impl FormatContext for ASTFormatContext { +impl FormatContext for ASTFormatContext<'_> { type Options = SimpleFormatOptions; fn options(&self) -> &Self::Options { &self.options } -} -impl ASTFormatContext { - pub fn contents(&self) -> Rc { - self.contents.clone() - } - - pub fn locator(&self) -> Locator { - Locator::new(&self.contents) + fn source_code(&self) -> SourceCode { + SourceCode::new(self.contents) } } diff --git a/crates/ruff_python_formatter/src/format/alias.rs b/crates/ruff_python_formatter/src/format/alias.rs index 5dba9a4bf4..1432b0c1c5 100644 --- a/crates/ruff_python_formatter/src/format/alias.rs +++ b/crates/ruff_python_formatter/src/format/alias.rs @@ -10,7 +10,7 @@ pub struct FormatAlias<'a> { item: &'a Alias, } -impl AsFormat for Alias { +impl AsFormat> for Alias { type Format<'a> = FormatAlias<'a>; fn format(&self) -> Self::Format<'_> { @@ -18,7 +18,7 @@ impl AsFormat for Alias { } } -impl Format for FormatAlias<'_> { +impl Format> for FormatAlias<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let alias = self.item; diff --git a/crates/ruff_python_formatter/src/format/arg.rs b/crates/ruff_python_formatter/src/format/arg.rs index cd4021fe05..b321b0145e 100644 --- a/crates/ruff_python_formatter/src/format/arg.rs +++ b/crates/ruff_python_formatter/src/format/arg.rs @@ -10,7 +10,7 @@ pub struct FormatArg<'a> { item: &'a Arg, } -impl AsFormat for Arg { +impl AsFormat> for Arg { type Format<'a> = FormatArg<'a>; fn format(&self) -> Self::Format<'_> { @@ -18,7 +18,7 @@ impl AsFormat for Arg { } } -impl Format for FormatArg<'_> { +impl Format> for FormatArg<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let arg = self.item; diff --git a/crates/ruff_python_formatter/src/format/arguments.rs b/crates/ruff_python_formatter/src/format/arguments.rs index d5dd17100d..38a536d5fe 100644 --- a/crates/ruff_python_formatter/src/format/arguments.rs +++ b/crates/ruff_python_formatter/src/format/arguments.rs @@ -9,7 +9,7 @@ pub struct FormatArguments<'a> { item: &'a Arguments, } -impl AsFormat for Arguments { +impl AsFormat> for Arguments { type Format<'a> = FormatArguments<'a>; fn format(&self) -> Self::Format<'_> { @@ -17,7 +17,7 @@ impl AsFormat for Arguments { } } -impl Format for FormatArguments<'_> { +impl Format> for FormatArguments<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let args = self.item; diff --git a/crates/ruff_python_formatter/src/format/bool_op.rs b/crates/ruff_python_formatter/src/format/bool_op.rs index e148c9c59c..eb705f0d47 100644 --- a/crates/ruff_python_formatter/src/format/bool_op.rs +++ b/crates/ruff_python_formatter/src/format/bool_op.rs @@ -10,7 +10,7 @@ pub struct FormatBoolOp<'a> { item: &'a BoolOp, } -impl AsFormat for BoolOp { +impl AsFormat> for BoolOp { type Format<'a> = FormatBoolOp<'a>; fn format(&self) -> Self::Format<'_> { @@ -18,7 +18,7 @@ impl AsFormat for BoolOp { } } -impl Format for FormatBoolOp<'_> { +impl Format> for FormatBoolOp<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let bool_op = self.item; write!(f, [leading_comments(bool_op)])?; diff --git a/crates/ruff_python_formatter/src/format/builders.rs b/crates/ruff_python_formatter/src/format/builders.rs index 38ef1806e5..3b2ec3c8da 100644 --- a/crates/ruff_python_formatter/src/format/builders.rs +++ b/crates/ruff_python_formatter/src/format/builders.rs @@ -12,7 +12,7 @@ pub(crate) struct Block<'a> { body: &'a Body, } -impl Format for Block<'_> { +impl Format> for Block<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { for (i, stmt) in self.body.iter().enumerate() { if i > 0 { @@ -28,7 +28,7 @@ impl Format for Block<'_> { write!(f, [empty_line()])?; } TriviaKind::OwnLineComment(range) => { - write!(f, [literal(range), hard_line_break()])?; + write!(f, [literal(range, ContainsNewlines::No), hard_line_break()])?; } _ => {} } @@ -49,7 +49,7 @@ pub(crate) struct Statements<'a> { suite: &'a [Stmt], } -impl Format for Statements<'_> { +impl Format> for Statements<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { for (i, stmt) in self.suite.iter().enumerate() { if i > 0 { @@ -70,20 +70,18 @@ pub(crate) struct Literal { range: TextRange, } -impl Format for Literal { +impl Format> for Literal { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { - let text = f.context().contents(); - - f.write_element(FormatElement::StaticTextSlice { - text, - range: self.range, - }) + source_text_slice(self.range, ContainsNewlines::Detect).fmt(f) } } #[inline] -pub(crate) const fn literal(range: TextRange) -> Literal { - Literal { range } +pub(crate) const fn literal( + range: TextRange, + newlines: ContainsNewlines, +) -> SourceTextSliceBuilder { + source_text_slice(range, newlines) } pub(crate) const fn join_names(names: &[String]) -> JoinNames { diff --git a/crates/ruff_python_formatter/src/format/cmp_op.rs b/crates/ruff_python_formatter/src/format/cmp_op.rs index da66c361c2..fe90676fc4 100644 --- a/crates/ruff_python_formatter/src/format/cmp_op.rs +++ b/crates/ruff_python_formatter/src/format/cmp_op.rs @@ -10,7 +10,7 @@ pub struct FormatCmpOp<'a> { item: &'a CmpOp, } -impl AsFormat for CmpOp { +impl AsFormat> for CmpOp { type Format<'a> = FormatCmpOp<'a>; fn format(&self) -> Self::Format<'_> { @@ -18,7 +18,7 @@ impl AsFormat for CmpOp { } } -impl Format for FormatCmpOp<'_> { +impl Format> for FormatCmpOp<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let cmp_op = self.item; write!(f, [leading_comments(cmp_op)])?; diff --git a/crates/ruff_python_formatter/src/format/comments.rs b/crates/ruff_python_formatter/src/format/comments.rs index 8aadf9a530..352bc85f7e 100644 --- a/crates/ruff_python_formatter/src/format/comments.rs +++ b/crates/ruff_python_formatter/src/format/comments.rs @@ -11,7 +11,7 @@ pub(crate) struct LeadingComments<'a, T> { item: &'a Attributed, } -impl Format for LeadingComments<'_, T> { +impl Format> for LeadingComments<'_, T> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { for trivia in &self.item.trivia { if trivia.relationship.is_leading() { @@ -20,7 +20,7 @@ impl Format for LeadingComments<'_, T> { write!(f, [empty_line()])?; } TriviaKind::OwnLineComment(range) => { - write!(f, [literal(range), hard_line_break()])?; + write!(f, [literal(range, ContainsNewlines::No), hard_line_break()])?; } _ => {} } @@ -40,7 +40,7 @@ pub(crate) struct TrailingComments<'a, T> { item: &'a Attributed, } -impl Format for TrailingComments<'_, T> { +impl Format> for TrailingComments<'_, T> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { for trivia in &self.item.trivia { if trivia.relationship.is_trailing() { @@ -49,7 +49,7 @@ impl Format for TrailingComments<'_, T> { write!(f, [empty_line()])?; } TriviaKind::OwnLineComment(range) => { - write!(f, [literal(range), hard_line_break()])?; + write!(f, [literal(range, ContainsNewlines::No), hard_line_break()])?; } _ => {} } @@ -69,7 +69,7 @@ pub(crate) struct EndOfLineComments<'a, T> { item: &'a Attributed, } -impl Format for EndOfLineComments<'_, T> { +impl Format> for EndOfLineComments<'_, T> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let mut first = true; for range in self @@ -81,7 +81,7 @@ impl Format for EndOfLineComments<'_, T> { if std::mem::take(&mut first) { write!(f, [line_suffix(&text(" "))])?; } - write!(f, [line_suffix(&literal(range))])?; + write!(f, [line_suffix(&literal(range, ContainsNewlines::No))])?; } Ok(()) } @@ -97,13 +97,13 @@ pub(crate) struct DanglingComments<'a, T> { item: &'a Attributed, } -impl Format for DanglingComments<'_, T> { +impl Format> for DanglingComments<'_, T> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { for trivia in &self.item.trivia { if trivia.relationship.is_dangling() { if let TriviaKind::OwnLineComment(range) = trivia.kind { write!(f, [hard_line_break()])?; - write!(f, [literal(range)])?; + write!(f, [literal(range, ContainsNewlines::No)])?; write!(f, [hard_line_break()])?; } } diff --git a/crates/ruff_python_formatter/src/format/comprehension.rs b/crates/ruff_python_formatter/src/format/comprehension.rs index 449aeba73a..9b572edcb2 100644 --- a/crates/ruff_python_formatter/src/format/comprehension.rs +++ b/crates/ruff_python_formatter/src/format/comprehension.rs @@ -9,7 +9,7 @@ pub struct FormatComprehension<'a> { item: &'a Comprehension, } -impl AsFormat for Comprehension { +impl AsFormat> for Comprehension { type Format<'a> = FormatComprehension<'a>; fn format(&self) -> Self::Format<'_> { @@ -17,7 +17,7 @@ impl AsFormat for Comprehension { } } -impl Format for FormatComprehension<'_> { +impl Format> for FormatComprehension<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let comprehension = self.item; diff --git a/crates/ruff_python_formatter/src/format/excepthandler.rs b/crates/ruff_python_formatter/src/format/excepthandler.rs index 6e348cc605..5619a95150 100644 --- a/crates/ruff_python_formatter/src/format/excepthandler.rs +++ b/crates/ruff_python_formatter/src/format/excepthandler.rs @@ -11,7 +11,7 @@ pub struct FormatExcepthandler<'a> { item: &'a Excepthandler, } -impl AsFormat for Excepthandler { +impl AsFormat> for Excepthandler { type Format<'a> = FormatExcepthandler<'a>; fn format(&self) -> Self::Format<'_> { @@ -19,7 +19,7 @@ impl AsFormat for Excepthandler { } } -impl Format for FormatExcepthandler<'_> { +impl Format> for FormatExcepthandler<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let excepthandler = self.item; let ExcepthandlerKind::ExceptHandler { type_, name, body } = &excepthandler.node; diff --git a/crates/ruff_python_formatter/src/format/expr.rs b/crates/ruff_python_formatter/src/format/expr.rs index f88f983d03..bc1bf911d6 100644 --- a/crates/ruff_python_formatter/src/format/expr.rs +++ b/crates/ruff_python_formatter/src/format/expr.rs @@ -33,7 +33,7 @@ fn format_starred( } fn format_name(f: &mut Formatter, expr: &Expr, _id: &str) -> FormatResult<()> { - write!(f, [literal(expr.range())])?; + write!(f, [literal(expr.range(), ContainsNewlines::No)])?; write!(f, [end_of_line_comments(expr)])?; Ok(()) } @@ -57,7 +57,7 @@ fn format_subscript( if let TriviaKind::OwnLineComment(range) = trivia.kind { write!(f, [expand_parent()])?; write!(f, [hard_line_break()])?; - write!(f, [literal(range)])?; + write!(f, [literal(range, ContainsNewlines::No)])?; } } } @@ -573,7 +573,7 @@ fn format_joined_str( expr: &Expr, _values: &[Expr], ) -> FormatResult<()> { - write!(f, [literal(expr.range())])?; + write!(f, [literal(expr.range(), ContainsNewlines::Detect)])?; write!(f, [end_of_line_comments(expr)])?; Ok(()) } @@ -800,7 +800,7 @@ fn format_if_exp( Ok(()) } -impl Format for FormatExpr<'_> { +impl Format> for FormatExpr<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { if self.item.parentheses.is_always() { write!(f, [text("(")])?; @@ -871,7 +871,7 @@ impl Format for FormatExpr<'_> { if trivia.relationship.is_trailing() { if let TriviaKind::OwnLineComment(range) = trivia.kind { write!(f, [expand_parent()])?; - write!(f, [literal(range)])?; + write!(f, [literal(range, ContainsNewlines::No)])?; write!(f, [hard_line_break()])?; } } @@ -885,7 +885,7 @@ impl Format for FormatExpr<'_> { } } -impl AsFormat for Expr { +impl AsFormat> for Expr { type Format<'a> = FormatExpr<'a>; fn format(&self) -> Self::Format<'_> { diff --git a/crates/ruff_python_formatter/src/format/keyword.rs b/crates/ruff_python_formatter/src/format/keyword.rs index 84191153d0..2f9a18be09 100644 --- a/crates/ruff_python_formatter/src/format/keyword.rs +++ b/crates/ruff_python_formatter/src/format/keyword.rs @@ -10,7 +10,7 @@ pub struct FormatKeyword<'a> { item: &'a Keyword, } -impl AsFormat for Keyword { +impl AsFormat> for Keyword { type Format<'a> = FormatKeyword<'a>; fn format(&self) -> Self::Format<'_> { @@ -18,7 +18,7 @@ impl AsFormat for Keyword { } } -impl Format for FormatKeyword<'_> { +impl Format> for FormatKeyword<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let keyword = self.item; diff --git a/crates/ruff_python_formatter/src/format/match_case.rs b/crates/ruff_python_formatter/src/format/match_case.rs index 858d62302c..6161e0f764 100644 --- a/crates/ruff_python_formatter/src/format/match_case.rs +++ b/crates/ruff_python_formatter/src/format/match_case.rs @@ -11,7 +11,7 @@ pub struct FormatMatchCase<'a> { item: &'a MatchCase, } -impl AsFormat for MatchCase { +impl AsFormat> for MatchCase { type Format<'a> = FormatMatchCase<'a>; fn format(&self) -> Self::Format<'_> { @@ -19,7 +19,7 @@ impl AsFormat for MatchCase { } } -impl Format for FormatMatchCase<'_> { +impl Format> for FormatMatchCase<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let MatchCase { pattern, diff --git a/crates/ruff_python_formatter/src/format/numbers.rs b/crates/ruff_python_formatter/src/format/numbers.rs index ca815f8042..a82d620c59 100644 --- a/crates/ruff_python_formatter/src/format/numbers.rs +++ b/crates/ruff_python_formatter/src/format/numbers.rs @@ -12,7 +12,7 @@ struct FloatAtom { range: TextRange, } -impl Format for FloatAtom { +impl Format> for FloatAtom { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let contents = f.context().contents(); @@ -26,12 +26,15 @@ impl Format for FloatAtom { } else { write!( f, - [literal(TextRange::new( - self.range.start(), - self.range - .start() - .add(TextSize::try_from(dot_index).unwrap()) - ))] + [literal( + TextRange::new( + self.range.start(), + self.range + .start() + .add(TextSize::try_from(dot_index).unwrap()) + ), + ContainsNewlines::No + )] )?; } @@ -42,16 +45,19 @@ impl Format for FloatAtom { } else { write!( f, - [literal(TextRange::new( - self.range - .start() - .add(TextSize::try_from(dot_index + 1).unwrap()), - self.range.end() - ))] + [literal( + TextRange::new( + self.range + .start() + .add(TextSize::try_from(dot_index + 1).unwrap()), + self.range.end() + ), + ContainsNewlines::No + )] )?; } } else { - write!(f, [literal(self.range)])?; + write!(f, [literal(self.range, ContainsNewlines::No)])?; } Ok(()) @@ -68,7 +74,7 @@ pub(crate) struct FloatLiteral { range: TextRange, } -impl Format for FloatLiteral { +impl Format> for FloatLiteral { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let contents = f.context().contents(); @@ -93,12 +99,15 @@ impl Format for FloatLiteral { let plus = content[exponent_index + 1..].starts_with('+'); write!( f, - [literal(TextRange::new( - self.range - .start() - .add(TextSize::try_from(exponent_index + 1 + usize::from(plus)).unwrap()), - self.range.end() - ))] + [literal( + TextRange::new( + self.range.start().add( + TextSize::try_from(exponent_index + 1 + usize::from(plus)).unwrap() + ), + self.range.end() + ), + ContainsNewlines::No + )] )?; } else { write!(f, [float_atom(self.range)])?; @@ -118,7 +127,7 @@ pub(crate) struct IntLiteral { range: TextRange, } -impl Format for IntLiteral { +impl Format> for IntLiteral { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let contents = f.context().contents(); @@ -142,14 +151,14 @@ impl Format for IntLiteral { )?; } else { // Use the existing source. - write!(f, [literal(self.range)])?; + write!(f, [literal(self.range, ContainsNewlines::No)])?; } return Ok(()); } } - write!(f, [literal(self.range)])?; + write!(f, [literal(self.range, ContainsNewlines::No)])?; Ok(()) } @@ -165,20 +174,20 @@ pub(crate) struct ComplexLiteral { range: TextRange, } -impl Format for ComplexLiteral { +impl Format> for ComplexLiteral { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let contents = f.context().contents(); let content = &contents[self.range]; if content.ends_with('j') { - write!(f, [literal(self.range)])?; + write!(f, [literal(self.range, ContainsNewlines::No)])?; } else if content.ends_with('J') { write!( f, - [literal(TextRange::new( - self.range.start(), - self.range.end().sub(TextSize::from(1)) - ))] + [literal( + TextRange::new(self.range.start(), self.range.end().sub(TextSize::from(1))), + ContainsNewlines::No + )] )?; write!(f, [text("j")])?; } else { diff --git a/crates/ruff_python_formatter/src/format/operator.rs b/crates/ruff_python_formatter/src/format/operator.rs index baede7c6cc..40c49893fc 100644 --- a/crates/ruff_python_formatter/src/format/operator.rs +++ b/crates/ruff_python_formatter/src/format/operator.rs @@ -10,7 +10,7 @@ pub struct FormatOperator<'a> { item: &'a Operator, } -impl AsFormat for Operator { +impl AsFormat> for Operator { type Format<'a> = FormatOperator<'a>; fn format(&self) -> Self::Format<'_> { @@ -18,7 +18,7 @@ impl AsFormat for Operator { } } -impl Format for FormatOperator<'_> { +impl Format> for FormatOperator<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let operator = self.item; write!(f, [leading_comments(operator)])?; diff --git a/crates/ruff_python_formatter/src/format/pattern.rs b/crates/ruff_python_formatter/src/format/pattern.rs index 93144b58a2..59093f4b61 100644 --- a/crates/ruff_python_formatter/src/format/pattern.rs +++ b/crates/ruff_python_formatter/src/format/pattern.rs @@ -11,7 +11,7 @@ pub struct FormatPattern<'a> { item: &'a Pattern, } -impl AsFormat for Pattern { +impl AsFormat> for Pattern { type Format<'a> = FormatPattern<'a>; fn format(&self) -> Self::Format<'_> { @@ -19,7 +19,7 @@ impl AsFormat for Pattern { } } -impl Format for FormatPattern<'_> { +impl Format> for FormatPattern<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let pattern = self.item; diff --git a/crates/ruff_python_formatter/src/format/stmt.rs b/crates/ruff_python_formatter/src/format/stmt.rs index 66517928d9..2b80453921 100644 --- a/crates/ruff_python_formatter/src/format/stmt.rs +++ b/crates/ruff_python_formatter/src/format/stmt.rs @@ -756,7 +756,7 @@ pub struct FormatStmt<'a> { item: &'a Stmt, } -impl Format for FormatStmt<'_> { +impl Format> for FormatStmt<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { write!(f, [leading_comments(self.item)])?; @@ -939,7 +939,7 @@ impl Format for FormatStmt<'_> { } } -impl AsFormat for Stmt { +impl AsFormat> for Stmt { type Format<'a> = FormatStmt<'a>; fn format(&self) -> Self::Format<'_> { diff --git a/crates/ruff_python_formatter/src/format/strings.rs b/crates/ruff_python_formatter/src/format/strings.rs index 62821cc56f..e5163cc532 100644 --- a/crates/ruff_python_formatter/src/format/strings.rs +++ b/crates/ruff_python_formatter/src/format/strings.rs @@ -13,7 +13,7 @@ pub(crate) struct StringLiteralPart { range: TextRange, } -impl Format for StringLiteralPart { +impl Format> for StringLiteralPart { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let contents = f.context().contents(); @@ -115,7 +115,7 @@ pub(crate) struct StringLiteral<'a> { expr: &'a Expr, } -impl Format for StringLiteral<'_> { +impl Format> for StringLiteral<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let expr = self.expr; diff --git a/crates/ruff_python_formatter/src/format/unary_op.rs b/crates/ruff_python_formatter/src/format/unary_op.rs index 39a16f52b5..5e51d251c2 100644 --- a/crates/ruff_python_formatter/src/format/unary_op.rs +++ b/crates/ruff_python_formatter/src/format/unary_op.rs @@ -9,7 +9,7 @@ pub struct FormatUnaryOp<'a> { item: &'a UnaryOp, } -impl AsFormat for UnaryOp { +impl AsFormat> for UnaryOp { type Format<'a> = FormatUnaryOp<'a>; fn format(&self) -> Self::Format<'_> { @@ -17,7 +17,7 @@ impl AsFormat for UnaryOp { } } -impl Format for FormatUnaryOp<'_> { +impl Format> for FormatUnaryOp<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let unary_op = self.item; write!( diff --git a/crates/ruff_python_formatter/src/format/withitem.rs b/crates/ruff_python_formatter/src/format/withitem.rs index 0a4b2e3852..50c309f86d 100644 --- a/crates/ruff_python_formatter/src/format/withitem.rs +++ b/crates/ruff_python_formatter/src/format/withitem.rs @@ -9,7 +9,7 @@ pub struct FormatWithitem<'a> { item: &'a Withitem, } -impl AsFormat for Withitem { +impl AsFormat> for Withitem { type Format<'a> = FormatWithitem<'a>; fn format(&self) -> Self::Format<'_> { @@ -17,7 +17,7 @@ impl AsFormat for Withitem { } } -impl Format for FormatWithitem<'_> { +impl Format> for FormatWithitem<'_> { fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { let withitem = self.item;