diff --git a/Cargo.lock b/Cargo.lock index b20ac59832..a308f50872 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -536,6 +536,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "countme" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -620,7 +626,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.7.1", "scopeguard", ] @@ -762,6 +768,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "drop_bomb" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bda8e21c04aca2ae33ffc2fd8c23134f3cac46db123ba97bd9d3f3b8a4a85e1" + [[package]] name = "dyn-clone" version = "1.0.10" @@ -798,6 +810,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_proxy" version = "0.4.1" @@ -1204,6 +1226,12 @@ dependencies = [ "want", ] +[[package]] +name = "iai" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678" + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -1646,6 +1674,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.7.1" @@ -2265,6 +2302,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "env_logger", + "log", + "rand", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.23" @@ -2557,6 +2616,23 @@ dependencies = [ "textwrap", ] +[[package]] +name = "ruff_formatter" +version = "0.0.0" +dependencies = [ + "cfg-if", + "countme", + "drop_bomb", + "indexmap", + "insta", + "ruff_rowan", + "rustc-hash", + "schemars", + "serde", + "tracing", + "unicode-width", +] + [[package]] name = "ruff_macros" version = "0.0.0" @@ -2577,6 +2653,45 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "ruff_rowan" +version = "0.0.0" +dependencies = [ + "countme", + "hashbrown", + "iai", + "memoffset 0.6.5", + "quickcheck", + "quickcheck_macros", + "ruff_text_edit", + "ruff_text_size", + "rustc-hash", + "schemars", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "ruff_text_edit" +version = "0.0.0" +dependencies = [ + "ruff_text_size", + "schemars", + "serde", + "similar", +] + +[[package]] +name = "ruff_text_size" +version = "0.0.0" +dependencies = [ + "schemars", + "serde", + "serde_test", + "static_assertions", +] + [[package]] name = "rust-stemmers" version = "1.2.0" @@ -2842,6 +2957,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3611210d2d67e3513204742004d6ac6f589e521861dabb0f649b070eea8bed9e" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2900,6 +3024,10 @@ name = "similar" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +dependencies = [ + "bstr 0.2.17", + "unicode-segmentation", +] [[package]] name = "siphasher" @@ -3518,6 +3646,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.10" diff --git a/LICENSE b/LICENSE index 8260021f01..fe0ba66590 100644 --- a/LICENSE +++ b/LICENSE @@ -1062,3 +1062,109 @@ are: """ - flake8-django, licensed under the GPL license. + +- rust-analyzer/rowan, licensed under the MIT license: + """ + Permission is hereby granted, free of charge, to any + person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the + Software without restriction, including without + limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software + is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice + shall be included in all copies or substantial portions + of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF + ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + """ + +- rust-analyzer/text-size, licensed under the MIT license: + """ + Permission is hereby granted, free of charge, to any + person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the + Software without restriction, including without + limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software + is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice + shall be included in all copies or substantial portions + of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF + ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + """ + +- rust-analyzer/text-edit, licensed under the MIT license: + """ + Permission is hereby granted, free of charge, to any + person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the + Software without restriction, including without + limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software + is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice + shall be included in all copies or substantial portions + of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF + ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED + TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT + SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + """ + +- rome/tools, licensed under the MIT license: + """ + MIT License + + Copyright (c) Rome Tools, Inc. and its affiliates. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + """ diff --git a/_typos.toml b/_typos.toml index 39136639ef..aef4905d73 100644 --- a/_typos.toml +++ b/_typos.toml @@ -1,2 +1,6 @@ [files] extend-exclude = ["snapshots"] + +[default.extend-words] +trivias = "trivias" +hel = "hel" diff --git a/crates/ruff_formatter/Cargo.toml b/crates/ruff_formatter/Cargo.toml new file mode 100644 index 0000000000..622d4b2e9c --- /dev/null +++ b/crates/ruff_formatter/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "ruff_formatter" +version = "0.0.0" +publish = false +edition = "2021" + +[dependencies] +cfg-if = { version = "1.0.0" } +countme = { version = "3.0.1" } +drop_bomb = { version = "0.1.5" } +indexmap = { version = "1.9.1" } +ruff_rowan = { path = "../ruff_rowan" } +rustc-hash = { workspace = true } +schemars = { version = "0.8.10", optional = true } +serde = { version = "1.0.136", features = ["derive"], optional = true } +tracing = { version = "0.1.31", default-features = false, features = ["std"] } +unicode-width = { version = "0.1.9" } + +[dev-dependencies] +insta = { version = "1.19.0" } + +[features] +serde = ["dep:serde", "schemars", "ruff_rowan/serde"] diff --git a/crates/ruff_formatter/src/arguments.rs b/crates/ruff_formatter/src/arguments.rs new file mode 100644 index 0000000000..d2fd95eecd --- /dev/null +++ b/crates/ruff_formatter/src/arguments.rs @@ -0,0 +1,160 @@ +use super::{Buffer, Format, Formatter}; +use crate::FormatResult; +use std::ffi::c_void; +use std::marker::PhantomData; + +/// Mono-morphed type to format an object. Used by the [crate::format!], [crate::format_args!], and +/// [crate::write!] macros. +/// +/// This struct is similar to a dynamic dispatch (using `dyn Format`) because it stores a pointer to the value. +/// However, it doesn't store the pointer to `dyn Format`'s vtable, instead it statically resolves the function +/// pointer of `Format::format` and stores it in `formatter`. +pub struct Argument<'fmt, Context> { + /// The value to format stored as a raw pointer where `lifetime` stores the value's lifetime. + value: *const c_void, + + /// Stores the lifetime of the value. To get the most out of our dear borrow checker. + lifetime: PhantomData<&'fmt ()>, + + /// The function pointer to `value`'s `Format::format` method + formatter: fn(*const c_void, &mut Formatter<'_, Context>) -> FormatResult<()>, +} + +impl Clone for Argument<'_, Context> { + fn clone(&self) -> Self { + *self + } +} +impl Copy for Argument<'_, Context> {} + +impl<'fmt, Context> Argument<'fmt, Context> { + /// Called by the [ruff_formatter::format_args] macro. Creates a mono-morphed value for formatting + /// an object. + #[doc(hidden)] + #[inline] + pub fn new>(value: &'fmt F) -> Self { + #[inline(always)] + fn formatter, Context>( + ptr: *const c_void, + fmt: &mut Formatter, + ) -> FormatResult<()> { + // SAFETY: Safe because the 'fmt lifetime is captured by the 'lifetime' field. + F::fmt(unsafe { &*(ptr as *const F) }, fmt) + } + + Self { + value: value as *const F as *const c_void, + lifetime: PhantomData, + formatter: formatter::, + } + } + + /// Formats the value stored by this argument using the given formatter. + #[inline(always)] + pub(super) fn format(&self, f: &mut Formatter) -> FormatResult<()> { + (self.formatter)(self.value, f) + } +} + +/// Sequence of objects that should be formatted in the specified order. +/// +/// The [`format_args!`] macro will safely create an instance of this structure. +/// +/// You can use the `Arguments` that [`format_args!]` return in `Format` context as seen below. +/// It will call the `format` function for every of it's objects. +/// +/// ```rust +/// use ruff_formatter::prelude::*; +/// use ruff_formatter::{format, format_args}; +/// +/// # fn main() -> FormatResult<()> { +/// let formatted = format!(SimpleFormatContext::default(), [ +/// format_args!(text("a"), space(), text("b")) +/// ])?; +/// +/// assert_eq!("a b", formatted.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +pub struct Arguments<'fmt, Context>(pub &'fmt [Argument<'fmt, Context>]); + +impl<'fmt, Context> Arguments<'fmt, Context> { + #[doc(hidden)] + #[inline(always)] + pub fn new(arguments: &'fmt [Argument<'fmt, Context>]) -> Self { + Self(arguments) + } + + /// Returns the arguments + #[inline] + pub(super) fn items(&self) -> &'fmt [Argument<'fmt, Context>] { + self.0 + } +} + +impl Copy for Arguments<'_, Context> {} + +impl Clone for Arguments<'_, Context> { + fn clone(&self) -> Self { + Self(self.0) + } +} + +impl Format for Arguments<'_, Context> { + #[inline(always)] + fn fmt(&self, formatter: &mut Formatter) -> FormatResult<()> { + formatter.write_fmt(*self) + } +} + +impl std::fmt::Debug for Arguments<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Arguments[...]") + } +} + +impl<'fmt, Context> From<&'fmt Argument<'fmt, Context>> for Arguments<'fmt, Context> { + fn from(argument: &'fmt Argument<'fmt, Context>) -> Self { + Arguments::new(std::slice::from_ref(argument)) + } +} + +#[cfg(test)] +mod tests { + use crate::format_element::tag::Tag; + use crate::prelude::*; + use crate::{format_args, write, FormatState, VecBuffer}; + + #[test] + fn test_nesting() { + let mut context = FormatState::new(()); + let mut buffer = VecBuffer::new(&mut context); + + write!( + &mut buffer, + [ + text("function"), + space(), + text("a"), + space(), + group(&format_args!(text("("), text(")"))) + ] + ) + .unwrap(); + + assert_eq!( + buffer.into_vec(), + vec![ + FormatElement::StaticText { text: "function" }, + FormatElement::Space, + FormatElement::StaticText { text: "a" }, + FormatElement::Space, + // Group + FormatElement::Tag(Tag::StartGroup(tag::Group::new())), + FormatElement::StaticText { text: "(" }, + FormatElement::StaticText { text: ")" }, + FormatElement::Tag(Tag::EndGroup) + ] + ); + } +} diff --git a/crates/ruff_formatter/src/buffer.rs b/crates/ruff_formatter/src/buffer.rs new file mode 100644 index 0000000000..342812e99e --- /dev/null +++ b/crates/ruff_formatter/src/buffer.rs @@ -0,0 +1,723 @@ +use super::{write, Arguments, FormatElement}; +use crate::format_element::Interned; +use crate::prelude::LineMode; +use crate::{Format, FormatResult, FormatState}; +use rustc_hash::FxHashMap; +use std::any::{Any, TypeId}; +use std::fmt::Debug; +use std::ops::{Deref, DerefMut}; + +/// A trait for writing or formatting into [FormatElement]-accepting buffers or streams. +pub trait Buffer { + /// The context used during formatting + type Context; + + /// Writes a [crate::FormatElement] into this buffer, returning whether the write succeeded. + /// + /// # Errors + /// This function will return an instance of [crate::FormatError] on error. + /// + /// # Examples + /// + /// ``` + /// use ruff_formatter::{Buffer, FormatElement, FormatState, SimpleFormatContext, VecBuffer}; + /// + /// let mut state = FormatState::new(SimpleFormatContext::default()); + /// let mut buffer = VecBuffer::new(&mut state); + /// + /// buffer.write_element(FormatElement::StaticText { text: "test"}).unwrap(); + /// + /// assert_eq!(buffer.into_vec(), vec![FormatElement::StaticText { text: "test" }]); + /// ``` + /// + fn write_element(&mut self, element: FormatElement) -> FormatResult<()>; + + /// Returns a slice containing all elements written into this buffer. + /// + /// Prefer using [BufferExtensions::start_recording] over accessing [Buffer::elements] directly. + #[doc(hidden)] + fn elements(&self) -> &[FormatElement]; + + /// Glue for usage of the [`write!`] macro with implementors of this trait. + /// + /// This method should generally not be invoked manually, but rather through the [`write!`] macro itself. + /// + /// # Examples + /// + /// ``` + /// use ruff_formatter::prelude::*; + /// use ruff_formatter::{Buffer, FormatState, SimpleFormatContext, VecBuffer, format_args}; + /// + /// let mut state = FormatState::new(SimpleFormatContext::default()); + /// let mut buffer = VecBuffer::new(&mut state); + /// + /// buffer.write_fmt(format_args!(text("Hello World"))).unwrap(); + /// + /// assert_eq!(buffer.into_vec(), vec![FormatElement::StaticText{ text: "Hello World" }]); + /// ``` + fn write_fmt(mut self: &mut Self, arguments: Arguments) -> FormatResult<()> { + write(&mut self, arguments) + } + + /// Returns the formatting state relevant for this formatting session. + fn state(&self) -> &FormatState; + + /// Returns the mutable formatting state relevant for this formatting session. + fn state_mut(&mut self) -> &mut FormatState; + + /// Takes a snapshot of the Buffers state, excluding the formatter state. + fn snapshot(&self) -> BufferSnapshot; + + /// Restores the snapshot buffer + /// + /// ## Panics + /// If the passed snapshot id is a snapshot of another buffer OR + /// if the snapshot is restored out of order + fn restore_snapshot(&mut self, snapshot: BufferSnapshot); +} + +/// Snapshot of a buffer state that can be restored at a later point. +/// +/// Used in cases where the formatting of an object fails but a parent formatter knows an alternative +/// strategy on how to format the object that might succeed. +#[derive(Debug)] +pub enum BufferSnapshot { + /// Stores an absolute position of a buffers state, for example, the offset of the last written element. + Position(usize), + + /// Generic structure for custom buffers that need to store more complex data. Slightly more + /// expensive because it requires allocating the buffer state on the heap. + Any(Box), +} + +impl BufferSnapshot { + /// Creates a new buffer snapshot that points to the specified position. + pub const fn position(index: usize) -> Self { + Self::Position(index) + } + + /// Unwraps the position value. + /// + /// # Panics + /// + /// If self is not a [`BufferSnapshot::Position`] + pub fn unwrap_position(&self) -> usize { + match self { + BufferSnapshot::Position(index) => *index, + BufferSnapshot::Any(_) => panic!("Tried to unwrap Any snapshot as a position."), + } + } + + /// Unwraps the any value. + /// + /// # Panics + /// + /// If `self` is not a [`BufferSnapshot::Any`]. + pub fn unwrap_any(self) -> T { + match self { + BufferSnapshot::Position(_) => { + panic!("Tried to unwrap Position snapshot as Any snapshot.") + } + BufferSnapshot::Any(value) => match value.downcast::() { + Ok(snapshot) => *snapshot, + Err(err) => { + panic!( + "Tried to unwrap snapshot of type {:?} as {:?}", + err.type_id(), + TypeId::of::() + ) + } + }, + } + } +} + +/// Implements the `[Buffer]` trait for all mutable references of objects implementing [Buffer]. +impl + ?Sized, Context> Buffer for &mut W { + type Context = Context; + + fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { + (**self).write_element(element) + } + + fn elements(&self) -> &[FormatElement] { + (**self).elements() + } + + fn write_fmt(&mut self, args: Arguments) -> FormatResult<()> { + (**self).write_fmt(args) + } + + fn state(&self) -> &FormatState { + (**self).state() + } + + fn state_mut(&mut self) -> &mut FormatState { + (**self).state_mut() + } + + fn snapshot(&self) -> BufferSnapshot { + (**self).snapshot() + } + + fn restore_snapshot(&mut self, snapshot: BufferSnapshot) { + (**self).restore_snapshot(snapshot) + } +} + +/// Vector backed [`Buffer`] implementation. +/// +/// The buffer writes all elements into the internal elements buffer. +#[derive(Debug)] +pub struct VecBuffer<'a, Context> { + state: &'a mut FormatState, + elements: Vec, +} + +impl<'a, Context> VecBuffer<'a, Context> { + pub fn new(state: &'a mut FormatState) -> Self { + Self::new_with_vec(state, Vec::new()) + } + + pub fn new_with_vec(state: &'a mut FormatState, elements: Vec) -> Self { + Self { state, elements } + } + + /// Creates a buffer with the specified capacity + pub fn with_capacity(capacity: usize, state: &'a mut FormatState) -> Self { + Self { + state, + elements: Vec::with_capacity(capacity), + } + } + + /// Consumes the buffer and returns the written [`FormatElement]`s as a vector. + pub fn into_vec(self) -> Vec { + self.elements + } + + /// Takes the elements without consuming self + pub fn take_vec(&mut self) -> Vec { + std::mem::take(&mut self.elements) + } +} + +impl Deref for VecBuffer<'_, Context> { + type Target = [FormatElement]; + + fn deref(&self) -> &Self::Target { + &self.elements + } +} + +impl DerefMut for VecBuffer<'_, Context> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.elements + } +} + +impl Buffer for VecBuffer<'_, Context> { + type Context = Context; + + fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { + self.elements.push(element); + + Ok(()) + } + + fn elements(&self) -> &[FormatElement] { + self + } + + fn state(&self) -> &FormatState { + self.state + } + + fn state_mut(&mut self) -> &mut FormatState { + self.state + } + + fn snapshot(&self) -> BufferSnapshot { + BufferSnapshot::position(self.elements.len()) + } + + fn restore_snapshot(&mut self, snapshot: BufferSnapshot) { + let position = snapshot.unwrap_position(); + assert!( + self.elements.len() >= position, + r#"Outdated snapshot. This buffer contains fewer elements than at the time the snapshot was taken. +Make sure that you take and restore the snapshot in order and that this snapshot belongs to the current buffer."# + ); + + self.elements.truncate(position); + } +} + +/// This struct wraps an existing buffer and emits a preamble text when the first text is written. +/// +/// This can be useful if you, for example, want to write some content if what gets written next isn't empty. +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::{FormatState, Formatted, PreambleBuffer, SimpleFormatContext, VecBuffer, write}; +/// use ruff_formatter::prelude::*; +/// +/// struct Preamble; +/// +/// impl Format for Preamble { +/// fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { +/// write!(f, [text("# heading"), hard_line_break()]) +/// } +/// } +/// +/// # fn main() -> FormatResult<()> { +/// let mut state = FormatState::new(SimpleFormatContext::default()); +/// let mut buffer = VecBuffer::new(&mut state); +/// +/// { +/// let mut with_preamble = PreambleBuffer::new(&mut buffer, Preamble); +/// +/// write!(&mut with_preamble, [text("this text will be on a new line")])?; +/// } +/// +/// let formatted = Formatted::new(Document::from(buffer.into_vec()), SimpleFormatContext::default()); +/// assert_eq!("# heading\nthis text will be on a new line", formatted.print()?.as_code()); +/// +/// # Ok(()) +/// # } +/// ``` +/// +/// The pre-amble does not get written if no content is written to the buffer. +/// +/// ``` +/// use ruff_formatter::{FormatState, Formatted, PreambleBuffer, SimpleFormatContext, VecBuffer, write}; +/// use ruff_formatter::prelude::*; +/// +/// struct Preamble; +/// +/// impl Format for Preamble { +/// fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { +/// write!(f, [text("# heading"), hard_line_break()]) +/// } +/// } +/// +/// # fn main() -> FormatResult<()> { +/// let mut state = FormatState::new(SimpleFormatContext::default()); +/// let mut buffer = VecBuffer::new(&mut state); +/// { +/// let mut with_preamble = PreambleBuffer::new(&mut buffer, Preamble); +/// } +/// +/// let formatted = Formatted::new(Document::from(buffer.into_vec()), SimpleFormatContext::default()); +/// assert_eq!("", formatted.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +pub struct PreambleBuffer<'buf, Preamble, Context> { + /// The wrapped buffer + inner: &'buf mut dyn Buffer, + + /// The pre-amble to write once the first content gets written to this buffer. + preamble: Preamble, + + /// Whether some content (including the pre-amble) has been written at this point. + empty: bool, +} + +impl<'buf, Preamble, Context> PreambleBuffer<'buf, Preamble, Context> { + pub fn new(inner: &'buf mut dyn Buffer, preamble: Preamble) -> Self { + Self { + inner, + preamble, + empty: true, + } + } + + /// Returns `true` if the preamble has been written, `false` otherwise. + pub fn did_write_preamble(&self) -> bool { + !self.empty + } +} + +impl Buffer for PreambleBuffer<'_, Preamble, Context> +where + Preamble: Format, +{ + type Context = Context; + + fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { + if self.empty { + write!(self.inner, [&self.preamble])?; + self.empty = false; + } + + self.inner.write_element(element) + } + + fn elements(&self) -> &[FormatElement] { + self.inner.elements() + } + + fn state(&self) -> &FormatState { + self.inner.state() + } + + fn state_mut(&mut self) -> &mut FormatState { + self.inner.state_mut() + } + + fn snapshot(&self) -> BufferSnapshot { + BufferSnapshot::Any(Box::new(PreambleBufferSnapshot { + inner: self.inner.snapshot(), + empty: self.empty, + })) + } + + fn restore_snapshot(&mut self, snapshot: BufferSnapshot) { + let snapshot = snapshot.unwrap_any::(); + + self.empty = snapshot.empty; + self.inner.restore_snapshot(snapshot.inner); + } +} + +struct PreambleBufferSnapshot { + inner: BufferSnapshot, + empty: bool, +} + +/// Buffer that allows you inspecting elements as they get written to the formatter. +pub struct Inspect<'inner, Context, Inspector> { + inner: &'inner mut dyn Buffer, + inspector: Inspector, +} + +impl<'inner, Context, Inspector> Inspect<'inner, Context, Inspector> { + fn new(inner: &'inner mut dyn Buffer, inspector: Inspector) -> Self { + Self { inner, inspector } + } +} + +impl<'inner, Context, Inspector> Buffer for Inspect<'inner, Context, Inspector> +where + Inspector: FnMut(&FormatElement), +{ + type Context = Context; + + fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { + (self.inspector)(&element); + self.inner.write_element(element) + } + + fn elements(&self) -> &[FormatElement] { + self.inner.elements() + } + + fn state(&self) -> &FormatState { + self.inner.state() + } + + fn state_mut(&mut self) -> &mut FormatState { + self.inner.state_mut() + } + + fn snapshot(&self) -> BufferSnapshot { + self.inner.snapshot() + } + + fn restore_snapshot(&mut self, snapshot: BufferSnapshot) { + self.inner.restore_snapshot(snapshot) + } +} + +/// A Buffer that removes any soft line breaks. +/// +/// * Removes [`lines`](FormatElement::Line) with the mode [`Soft`](LineMode::Soft). +/// * Replaces [`lines`](FormatElement::Line) with the mode [`Soft`](LineMode::SoftOrSpace) with a [`Space`](FormatElement::Space) +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::prelude::*; +/// use ruff_formatter::{format, write}; +/// +/// # fn main() -> FormatResult<()> { +/// use ruff_formatter::{RemoveSoftLinesBuffer, SimpleFormatContext, VecBuffer}; +/// use ruff_formatter::prelude::format_with; +/// let formatted = format!( +/// SimpleFormatContext::default(), +/// [format_with(|f| { +/// let mut buffer = RemoveSoftLinesBuffer::new(f); +/// +/// write!( +/// buffer, +/// [ +/// text("The next soft line or space gets replaced by a space"), +/// soft_line_break_or_space(), +/// text("and the line here"), +/// soft_line_break(), +/// text("is removed entirely.") +/// ] +/// ) +/// })] +/// )?; +/// +/// assert_eq!( +/// formatted.document().as_ref(), +/// &[ +/// FormatElement::StaticText { text: "The next soft line or space gets replaced by a space" }, +/// FormatElement::Space, +/// FormatElement::StaticText { text: "and the line here" }, +/// FormatElement::StaticText { text: "is removed entirely." } +/// ] +/// ); +/// +/// # Ok(()) +/// # } +/// ``` +pub struct RemoveSoftLinesBuffer<'a, Context> { + inner: &'a mut dyn Buffer, + + /// Caches the interned elements after the soft line breaks have been removed. + /// + /// The `key` is the [Interned] element as it has been passed to [Self::write_element] or the child of another + /// [Interned] element. The `value` is the matching document of the key where all soft line breaks have been removed. + /// + /// It's fine to not snapshot the cache. The worst that can happen is that it holds on interned elements + /// that are now unused. But there's little harm in that and the cache is cleaned when dropping the buffer. + interned_cache: FxHashMap, +} + +impl<'a, Context> RemoveSoftLinesBuffer<'a, Context> { + /// Creates a new buffer that removes the soft line breaks before writing them into `buffer`. + pub fn new(inner: &'a mut dyn Buffer) -> Self { + Self { + inner, + interned_cache: FxHashMap::default(), + } + } + + /// Removes the soft line breaks from an interned element. + fn clean_interned(&mut self, interned: &Interned) -> Interned { + clean_interned(interned, &mut self.interned_cache) + } +} + +// Extracted to function to avoid monomorphization +fn clean_interned( + interned: &Interned, + interned_cache: &mut FxHashMap, +) -> Interned { + match interned_cache.get(interned) { + Some(cleaned) => cleaned.clone(), + None => { + // Find the first soft line break element or interned element that must be changed + let result = interned + .iter() + .enumerate() + .find_map(|(index, element)| match element { + FormatElement::Line(LineMode::Soft | LineMode::SoftOrSpace) => { + let mut cleaned = Vec::new(); + cleaned.extend_from_slice(&interned[..index]); + Some((cleaned, &interned[index..])) + } + FormatElement::Interned(inner) => { + let cleaned_inner = clean_interned(inner, interned_cache); + + if &cleaned_inner != inner { + let mut cleaned = Vec::with_capacity(interned.len()); + cleaned.extend_from_slice(&interned[..index]); + cleaned.push(FormatElement::Interned(cleaned_inner)); + Some((cleaned, &interned[index + 1..])) + } else { + None + } + } + + _ => None, + }); + + let result = match result { + // Copy the whole interned buffer so that becomes possible to change the necessary elements. + Some((mut cleaned, rest)) => { + for element in rest { + let element = match element { + FormatElement::Line(LineMode::Soft) => continue, + FormatElement::Line(LineMode::SoftOrSpace) => FormatElement::Space, + FormatElement::Interned(interned) => { + FormatElement::Interned(clean_interned(interned, interned_cache)) + } + element => element.clone(), + }; + cleaned.push(element) + } + + Interned::new(cleaned) + } + // No change necessary, return existing interned element + None => interned.clone(), + }; + + interned_cache.insert(interned.clone(), result.clone()); + result + } + } +} + +impl Buffer for RemoveSoftLinesBuffer<'_, Context> { + type Context = Context; + + fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { + let element = match element { + FormatElement::Line(LineMode::Soft) => return Ok(()), + FormatElement::Line(LineMode::SoftOrSpace) => FormatElement::Space, + FormatElement::Interned(interned) => { + FormatElement::Interned(self.clean_interned(&interned)) + } + element => element, + }; + + self.inner.write_element(element) + } + + fn elements(&self) -> &[FormatElement] { + self.inner.elements() + } + + fn state(&self) -> &FormatState { + self.inner.state() + } + + fn state_mut(&mut self) -> &mut FormatState { + self.inner.state_mut() + } + + fn snapshot(&self) -> BufferSnapshot { + self.inner.snapshot() + } + + fn restore_snapshot(&mut self, snapshot: BufferSnapshot) { + self.inner.restore_snapshot(snapshot) + } +} + +pub trait BufferExtensions: Buffer + Sized { + /// Returns a new buffer that calls the passed inspector for every element that gets written to the output + #[must_use] + fn inspect(&mut self, inspector: F) -> Inspect + where + F: FnMut(&FormatElement), + { + Inspect::new(self, inspector) + } + + /// Starts a recording that gives you access to all elements that have been written between the start + /// and end of the recording + /// + /// #Examples + /// + /// ``` + /// use std::ops::Deref; + /// use ruff_formatter::prelude::*; + /// use ruff_formatter::{write, format, SimpleFormatContext}; + /// + /// # fn main() -> FormatResult<()> { + /// let formatted = format!(SimpleFormatContext::default(), [format_with(|f| { + /// let mut recording = f.start_recording(); + /// + /// write!(recording, [text("A")])?; + /// write!(recording, [text("B")])?; + /// + /// write!(recording, [format_with(|f| write!(f, [text("C"), text("D")]))])?; + /// + /// let recorded = recording.stop(); + /// assert_eq!( + /// recorded.deref(), + /// &[ + /// FormatElement::StaticText{ text: "A" }, + /// FormatElement::StaticText{ text: "B" }, + /// FormatElement::StaticText{ text: "C" }, + /// FormatElement::StaticText{ text: "D" } + /// ] + /// ); + /// + /// Ok(()) + /// })])?; + /// + /// assert_eq!(formatted.print()?.as_code(), "ABCD"); + /// # Ok(()) + /// # } + /// ``` + #[must_use] + fn start_recording(&mut self) -> Recording { + Recording::new(self) + } + + /// Writes a sequence of elements into this buffer. + fn write_elements(&mut self, elements: I) -> FormatResult<()> + where + I: IntoIterator, + { + for element in elements.into_iter() { + self.write_element(element)?; + } + + Ok(()) + } +} + +impl BufferExtensions for T where T: Buffer {} + +#[derive(Debug)] +pub struct Recording<'buf, Buffer> { + start: usize, + buffer: &'buf mut Buffer, +} + +impl<'buf, B> Recording<'buf, B> +where + B: Buffer, +{ + fn new(buffer: &'buf mut B) -> Self { + Self { + start: buffer.elements().len(), + buffer, + } + } + + #[inline(always)] + pub fn write_fmt(&mut self, arguments: Arguments) -> FormatResult<()> { + self.buffer.write_fmt(arguments) + } + + #[inline(always)] + pub fn write_element(&mut self, element: FormatElement) -> FormatResult<()> { + self.buffer.write_element(element) + } + + pub fn stop(self) -> Recorded<'buf> { + let buffer: &'buf B = self.buffer; + let elements = buffer.elements(); + + let recorded = if self.start > elements.len() { + // May happen if buffer was rewinded. + &[] + } else { + &elements[self.start..] + }; + + Recorded(recorded) + } +} + +#[derive(Debug, Copy, Clone)] +pub struct Recorded<'a>(&'a [FormatElement]); + +impl Deref for Recorded<'_> { + type Target = [FormatElement]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} diff --git a/crates/ruff_formatter/src/builders.rs b/crates/ruff_formatter/src/builders.rs new file mode 100644 index 0000000000..cee634b52e --- /dev/null +++ b/crates/ruff_formatter/src/builders.rs @@ -0,0 +1,2243 @@ +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, TextRange, TextSize}; +use crate::{Buffer, VecBuffer}; +use ruff_rowan::{Language, SyntaxNode, SyntaxToken, SyntaxTokenText, TextLen}; +use std::borrow::Cow; +use std::cell::Cell; +use std::marker::PhantomData; +use std::num::NonZeroU8; +use Tag::*; + +/// A line break that only gets printed if the enclosing `Group` doesn't fit on a single line. +/// It's omitted if the enclosing `Group` fits on a single line. +/// A soft line break is identical to a hard line break when not enclosed inside of a `Group`. +/// +/// # Examples +/// +/// Soft line breaks are omitted if the enclosing `Group` fits on a single line +/// +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![text("a,"), soft_line_break(), text("b")]) +/// ])?; +/// +/// assert_eq!( +/// "a,b", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// See [soft_line_break_or_space] if you want to insert a space between the elements if the enclosing +/// `Group` fits on a single line. +/// +/// Soft line breaks are emitted if the enclosing `Group` doesn't fit on a single line +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(10).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("a long word,"), +/// soft_line_break(), +/// text("so that the group doesn't fit on a single line"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "a long word,\nso that the group doesn't fit on a single line", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub const fn soft_line_break() -> Line { + Line::new(LineMode::Soft) +} + +/// A forced line break that are always printed. A hard line break forces any enclosing `Group` +/// to be printed over multiple lines. +/// +/// # Examples +/// +/// It forces a line break, even if the enclosing `Group` would otherwise fit on a single line. +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("a,"), +/// hard_line_break(), +/// text("b"), +/// hard_line_break() +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "a,\nb\n", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub const fn hard_line_break() -> Line { + Line::new(LineMode::Hard) +} + +/// A forced empty line. An empty line inserts enough line breaks in the output for +/// the previous and next element to be separated by an empty line. +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// fn main() -> FormatResult<()> { +/// let elements = format!( +/// SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("a,"), +/// empty_line(), +/// text("b"), +/// empty_line() +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "a,\n\nb\n\n", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub const fn empty_line() -> Line { + Line::new(LineMode::Empty) +} + +/// A line break if the enclosing `Group` doesn't fit on a single line, a space otherwise. +/// +/// # Examples +/// +/// The line breaks are emitted as spaces if the enclosing `Group` fits on a a single line: +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("a,"), +/// soft_line_break_or_space(), +/// text("b"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "a, b", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// The printer breaks the lines if the enclosing `Group` doesn't fit on a single line: +/// ``` +/// use ruff_formatter::{format_args, format, LineWidth, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(10).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("a long word,"), +/// soft_line_break_or_space(), +/// text("so that the group doesn't fit on a single line"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "a long word,\nso that the group doesn't fit on a single line", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub const fn soft_line_break_or_space() -> Line { + Line::new(LineMode::SoftOrSpace) +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Line { + mode: LineMode, +} + +impl Line { + const fn new(mode: LineMode) -> Self { + Self { mode } + } +} + +impl Format for Line { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Line(self.mode)) + } +} + +impl std::fmt::Debug for Line { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Line").field(&self.mode).finish() + } +} + +/// Creates a token that gets written as is to the output. Make sure to properly escape the text if +/// it's user generated (e.g. a string and not a language keyword). +/// +/// # Line feeds +/// Tokens may contain line breaks but they must use the line feeds (`\n`). +/// The [crate::Printer] converts the line feed characters to the character specified in the [crate::PrinterOptions]. +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::format; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [text("Hello World")])?; +/// +/// assert_eq!( +/// "Hello World", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// Printing a string literal as a literal requires that the string literal is properly escaped and +/// enclosed in quotes (depending on the target language). +/// +/// ``` +/// use ruff_formatter::format; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// // the tab must be encoded as \\t to not literally print a tab character ("Hello{tab}World" vs "Hello\tWorld") +/// let elements = format!(SimpleFormatContext::default(), [text("\"Hello\\tWorld\"")])?; +/// +/// assert_eq!(r#""Hello\tWorld""#, elements.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn text(text: &'static str) -> StaticText { + debug_assert_no_newlines(text); + + StaticText { text } +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct StaticText { + text: &'static str, +} + +impl Format for StaticText { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::StaticText { text: self.text }) + } +} + +impl std::fmt::Debug for StaticText { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::write!(f, "StaticToken({})", self.text) + } +} + +/// Creates a text from a dynamic string and a range of the input source +pub fn dynamic_text(text: &str, position: TextSize) -> DynamicText { + debug_assert_no_newlines(text); + + DynamicText { text, position } +} + +#[derive(Eq, PartialEq)] +pub struct DynamicText<'a> { + text: &'a str, + position: TextSize, +} + +impl Format for DynamicText<'_> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::DynamicText { + text: self.text.to_string().into_boxed_str(), + source_position: self.position, + }) + } +} + +impl std::fmt::Debug for DynamicText<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::write!(f, "DynamicToken({})", self.text) + } +} + +/// String that is the same as in the input source text if `text` is [`Cow::Borrowed`] or +/// some replaced content if `text` is [`Cow::Owned`]. +pub fn syntax_token_cow_slice<'a, L: Language>( + text: Cow<'a, str>, + token: &'a SyntaxToken, + start: TextSize, +) -> SyntaxTokenCowSlice<'a, L> { + debug_assert_no_newlines(&text); + + SyntaxTokenCowSlice { text, token, start } +} + +pub struct SyntaxTokenCowSlice<'a, L: Language> { + text: Cow<'a, str>, + token: &'a SyntaxToken, + start: TextSize, +} + +impl Format for SyntaxTokenCowSlice<'_, L> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + match &self.text { + Cow::Borrowed(text) => { + let range = TextRange::at(self.start, text.text_len()); + debug_assert_eq!( + *text, + &self.token.text()[range - self.token.text_range().start()], + "The borrowed string doesn't match the specified token substring. Does the borrowed string belong to this token and range?" + ); + + let relative_range = range - self.token.text_range().start(); + let slice = self.token.token_text().slice(relative_range); + + f.write_element(FormatElement::SyntaxTokenTextSlice { + slice, + source_position: self.start, + }) + } + Cow::Owned(text) => f.write_element(FormatElement::DynamicText { + text: text.to_string().into_boxed_str(), + source_position: self.start, + }), + } + } +} + +impl std::fmt::Debug for SyntaxTokenCowSlice<'_, L> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::write!(f, "SyntaxTokenCowSlice({})", self.text) + } +} + +/// Copies a source text 1:1 into the output text. +pub fn syntax_token_text_slice( + token: &SyntaxToken, + range: TextRange, +) -> SyntaxTokenTextSlice { + let relative_range = range - token.text_range().start(); + let slice = token.token_text().slice(relative_range); + + debug_assert_no_newlines(&slice); + + SyntaxTokenTextSlice { + text: slice, + source_position: range.start(), + } +} + +pub struct SyntaxTokenTextSlice { + text: SyntaxTokenText, + source_position: TextSize, +} + +impl Format for SyntaxTokenTextSlice { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::SyntaxTokenTextSlice { + slice: self.text.clone(), + source_position: self.source_position, + }) + } +} + +impl std::fmt::Debug for SyntaxTokenTextSlice { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::write!(f, "SyntaxTokenTextSlice({})", self.text) + } +} + +fn debug_assert_no_newlines(text: &str) { + debug_assert!(!text.contains('\r'), "The content '{}' contains an unsupported '\\r' line terminator character but text must only use line feeds '\\n' as line separator. Use '\\n' instead of '\\r' and '\\r\\n' to insert a line break in strings.", text); +} + +/// Pushes some content to the end of the current line +/// +/// ## Examples +/// +/// ``` +/// use ruff_formatter::{format}; +/// use ruff_formatter::prelude::*; +/// +/// fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// text("a"), +/// line_suffix(&text("c")), +/// text("b") +/// ])?; +/// +/// assert_eq!( +/// "abc", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn line_suffix(inner: &Content) -> LineSuffix +where + Content: Format, +{ + LineSuffix { + content: Argument::new(inner), + } +} + +#[derive(Copy, Clone)] +pub struct LineSuffix<'a, Context> { + content: Argument<'a, Context>, +} + +impl Format for LineSuffix<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartLineSuffix))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndLineSuffix)) + } +} + +impl std::fmt::Debug for LineSuffix<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("LineSuffix").field(&"{{content}}").finish() + } +} + +/// Inserts a boundary for line suffixes that forces the printer to print all pending line suffixes. +/// Helpful if a line sufix shouldn't pass a certain point. +/// +/// ## Examples +/// +/// Forces the line suffix "c" to be printed before the token `d`. +/// ``` +/// use ruff_formatter::format; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// text("a"), +/// line_suffix(&text("c")), +/// text("b"), +/// line_suffix_boundary(), +/// text("d") +/// ])?; +/// +/// assert_eq!( +/// "abc\nd", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +pub const fn line_suffix_boundary() -> LineSuffixBoundary { + LineSuffixBoundary +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct LineSuffixBoundary; + +impl Format for LineSuffixBoundary { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::LineSuffixBoundary) + } +} + +/// Marks some content with a label. +/// +/// This does not directly influence how this content will be printed, but some +/// parts of the formatter may inspect the [labelled element](Tag::StartLabelled) +/// using [FormatElements::has_label]. +/// +/// ## Examples +/// +/// ```rust +/// use ruff_formatter::prelude::*; +/// use ruff_formatter::{format, write, LineWidth}; +/// +/// enum SomeLabelId {} +/// +/// # fn main() -> FormatResult<()> { +/// let formatted = format!( +/// SimpleFormatContext::default(), +/// [format_with(|f| { +/// let mut recording = f.start_recording(); +/// write!(recording, [ +/// labelled( +/// LabelId::of::(), +/// &text("'I have a label'") +/// ) +/// ])?; +/// +/// let recorded = recording.stop(); +/// +/// let is_labelled = recorded.first().map_or(false, |element| element.has_label(LabelId::of::())); +/// +/// if is_labelled { +/// write!(f, [text(" has label SomeLabelId")]) +/// } else { +/// write!(f, [text(" doesn't have label SomeLabelId")]) +/// } +/// })] +/// )?; +/// +/// assert_eq!("'I have a label' has label SomeLabelId", formatted.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +/// +/// ## Alternatives +/// +/// Use `Memoized.inspect(f)?.has_label(LabelId::of::()` if you need to know if some content breaks that should +/// only be written later. +#[inline] +pub fn labelled(label_id: LabelId, content: &Content) -> FormatLabelled +where + Content: Format, +{ + FormatLabelled { + label_id, + content: Argument::new(content), + } +} + +#[derive(Copy, Clone)] +pub struct FormatLabelled<'a, Context> { + label_id: LabelId, + content: Argument<'a, Context>, +} + +impl Format for FormatLabelled<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartLabelled(self.label_id)))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndLabelled)) + } +} + +impl std::fmt::Debug for FormatLabelled<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Label") + .field(&self.label_id) + .field(&"{{content}}") + .finish() + } +} + +/// Inserts a single space. Allows to separate different tokens. +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::format; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// // the tab must be encoded as \\t to not literally print a tab character ("Hello{tab}World" vs "Hello\tWorld") +/// let elements = format!(SimpleFormatContext::default(), [text("a"), space(), text("b")])?; +/// +/// assert_eq!("a b", elements.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub const fn space() -> Space { + Space +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct Space; + +impl Format for Space { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Space) + } +} + +/// It adds a level of indentation to the given content +/// +/// It doesn't add any line breaks at the edges of the content, meaning that +/// the line breaks have to be manually added. +/// +/// This helper should be used only in rare cases, instead you should rely more on +/// [block_indent] and [soft_block_indent] +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let block = format!(SimpleFormatContext::default(), [ +/// text("switch {"), +/// block_indent(&format_args![ +/// text("default:"), +/// indent(&format_args![ +/// // this is where we want to use a +/// hard_line_break(), +/// text("break;"), +/// ]) +/// ]), +/// text("}"), +/// ])?; +/// +/// assert_eq!( +/// "switch {\n\tdefault:\n\t\tbreak;\n}", +/// block.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn indent(content: &Content) -> Indent +where + Content: Format, +{ + Indent { + content: Argument::new(content), + } +} + +#[derive(Copy, Clone)] +pub struct Indent<'a, Context> { + content: Argument<'a, Context>, +} + +impl Format for Indent<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartIndent))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndIndent)) + } +} + +impl std::fmt::Debug for Indent<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Indent").field(&"{{content}}").finish() + } +} + +/// It reduces the indention for the given content depending on the closest [indent] or [align] parent element. +/// * [align] Undoes the spaces added by [align] +/// * [indent] Reduces the indention level by one +/// +/// This is a No-op if the indention level is zero. +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let block = format!(SimpleFormatContext::default(), [ +/// text("root"), +/// align(2, &format_args![ +/// hard_line_break(), +/// text("aligned"), +/// dedent(&format_args![ +/// hard_line_break(), +/// text("not aligned"), +/// ]), +/// dedent(&indent(&format_args![ +/// hard_line_break(), +/// text("Indented, not aligned") +/// ])) +/// ]), +/// dedent(&format_args![ +/// hard_line_break(), +/// text("Dedent on root level is a no-op.") +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "root\n aligned\nnot aligned\n\tIndented, not aligned\nDedent on root level is a no-op.", +/// block.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn dedent(content: &Content) -> Dedent +where + Content: Format, +{ + Dedent { + content: Argument::new(content), + mode: DedentMode::Level, + } +} + +#[derive(Copy, Clone)] +pub struct Dedent<'a, Context> { + content: Argument<'a, Context>, + mode: DedentMode, +} + +impl Format for Dedent<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartDedent(self.mode)))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndDedent)) + } +} + +impl std::fmt::Debug for Dedent<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Dedent").field(&"{{content}}").finish() + } +} + +/// It resets the indent document so that the content will be printed at the start of the line. +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let block = format!(SimpleFormatContext::default(), [ +/// text("root"), +/// indent(&format_args![ +/// hard_line_break(), +/// text("indent level 1"), +/// indent(&format_args![ +/// hard_line_break(), +/// text("indent level 2"), +/// align(2, &format_args![ +/// hard_line_break(), +/// text("two space align"), +/// dedent_to_root(&format_args![ +/// hard_line_break(), +/// text("starts at the beginning of the line") +/// ]), +/// ]), +/// hard_line_break(), +/// text("end indent level 2"), +/// ]) +/// ]), +/// ])?; +/// +/// assert_eq!( +/// "root\n\tindent level 1\n\t\tindent level 2\n\t\t two space align\nstarts at the beginning of the line\n\t\tend indent level 2", +/// block.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// ## Prettier +/// +/// This resembles the behaviour of Prettier's `align(Number.NEGATIVE_INFINITY, content)` IR element. +#[inline] +pub fn dedent_to_root(content: &Content) -> Dedent +where + Content: Format, +{ + Dedent { + content: Argument::new(content), + mode: DedentMode::Root, + } +} + +/// Aligns its content by indenting the content by `count` spaces. +/// +/// [align] is a variant of `[indent]` that indents its content by a specified number of spaces rather than +/// using the configured indent character (tab or a specified number of spaces). +/// +/// You should use [align] when you want to indent a content by a specific number of spaces. +/// Using [indent] is preferred in all other situations as it respects the users preferred indent character. +/// +/// # Examples +/// +/// ## Tab indention +/// +/// ``` +/// use std::num::NonZeroU8; +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let block = format!(SimpleFormatContext::default(), [ +/// text("a"), +/// hard_line_break(), +/// text("?"), +/// space(), +/// align(2, &format_args![ +/// text("function () {"), +/// hard_line_break(), +/// text("}"), +/// ]), +/// hard_line_break(), +/// text(":"), +/// space(), +/// align(2, &format_args![ +/// text("function () {"), +/// block_indent(&text("console.log('test');")), +/// text("}"), +/// ]), +/// text(";") +/// ])?; +/// +/// assert_eq!( +/// "a\n? function () {\n }\n: function () {\n\t\tconsole.log('test');\n };", +/// block.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// You can see that: +/// +/// * the printer indents the function's `}` by two spaces because it is inside of an `align`. +/// * the block `console.log` gets indented by two tabs. +/// This is because `align` increases the indention level by one (same as `indent`) +/// if you nest an `indent` inside an `align`. +/// Meaning that, `align > ... > indent` results in the same indention as `indent > ... > indent`. +/// +/// ## Spaces indention +/// +/// ``` +/// use std::num::NonZeroU8; +/// use ruff_formatter::{format, format_args, IndentStyle, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// indent_style: IndentStyle::Space(4), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let block = format!(context, [ +/// text("a"), +/// hard_line_break(), +/// text("?"), +/// space(), +/// align(2, &format_args![ +/// text("function () {"), +/// hard_line_break(), +/// text("}"), +/// ]), +/// hard_line_break(), +/// text(":"), +/// space(), +/// align(2, &format_args![ +/// text("function () {"), +/// block_indent(&text("console.log('test');")), +/// text("}"), +/// ]), +/// text(";") +/// ])?; +/// +/// assert_eq!( +/// "a\n? function () {\n }\n: function () {\n console.log('test');\n };", +/// block.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// The printing of `align` differs if using spaces as indention sequence *and* it contains an `indent`. +/// You can see the difference when comparing the indention of the `console.log(...)` expression to the previous example: +/// +/// * tab indention: Printer indents the expression with two tabs because the `align` increases the indention level. +/// * space indention: Printer indents the expression by 4 spaces (one indention level) **and** 2 spaces for the align. +pub fn align(count: u8, content: &Content) -> Align +where + Content: Format, +{ + Align { + count: NonZeroU8::new(count).expect("Alignment count must be a non-zero number."), + content: Argument::new(content), + } +} + +#[derive(Copy, Clone)] +pub struct Align<'a, Context> { + count: NonZeroU8, + content: Argument<'a, Context>, +} + +impl Format for Align<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartAlign(tag::Align(self.count))))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndAlign)) + } +} + +impl std::fmt::Debug for Align<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Align") + .field("count", &self.count) + .field("content", &"{{content}}") + .finish() + } +} + +/// Inserts a hard line break before and after the content and increases the indention level for the content by one. +/// +/// Block indents indent a block of code, such as in a function body, and therefore insert a line +/// break before and after the content. +/// +/// Doesn't create an indention if the passed in content is [FormatElement.is_empty]. +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let block = format![ +/// SimpleFormatContext::default(), +/// [ +/// text("{"), +/// block_indent(&format_args![ +/// text("let a = 10;"), +/// hard_line_break(), +/// text("let c = a + 5;"), +/// ]), +/// text("}"), +/// ] +/// ]?; +/// +/// assert_eq!( +/// "{\n\tlet a = 10;\n\tlet c = a + 5;\n}", +/// block.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn block_indent(content: &impl Format) -> BlockIndent { + BlockIndent { + content: Argument::new(content), + mode: IndentMode::Block, + } +} + +/// Indents the content by inserting a line break before and after the content and increasing +/// the indention level for the content by one if the enclosing group doesn't fit on a single line. +/// Doesn't change the formatting if the enclosing group fits on a single line. +/// +/// # Examples +/// +/// Indents the content by one level and puts in new lines if the enclosing `Group` doesn't fit on a single line +/// +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(10).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("'First string',"), +/// soft_line_break_or_space(), +/// text("'second string',"), +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[\n\t'First string',\n\t'second string',\n]", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// Doesn't change the formatting if the enclosing `Group` fits on a single line +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("5,"), +/// soft_line_break_or_space(), +/// text("10"), +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[5, 10]", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn soft_block_indent(content: &impl Format) -> BlockIndent { + BlockIndent { + content: Argument::new(content), + mode: IndentMode::Soft, + } +} + +/// If the enclosing `Group` doesn't fit on a single line, inserts a line break and indent. +/// Otherwise, just inserts a space. +/// +/// Line indents are used to break a single line of code, and therefore only insert a line +/// break before the content and not after the content. +/// +/// # Examples +/// +/// Indents the content by one level and puts in new lines if the enclosing `Group` doesn't +/// fit on a single line. Otherwise, just inserts a space. +/// +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(10).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("name"), +/// space(), +/// text("="), +/// soft_line_indent_or_space(&format_args![ +/// text("firstName"), +/// space(), +/// text("+"), +/// space(), +/// text("lastName"), +/// ]), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "name =\n\tfirstName + lastName", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// Only adds a space if the enclosing `Group` fits on a single line +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("a"), +/// space(), +/// text("="), +/// soft_line_indent_or_space(&text("10")), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "a = 10", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn soft_line_indent_or_space(content: &impl Format) -> BlockIndent { + BlockIndent { + content: Argument::new(content), + mode: IndentMode::SoftLineOrSpace, + } +} + +#[derive(Copy, Clone)] +pub struct BlockIndent<'a, Context> { + content: Argument<'a, Context>, + mode: IndentMode, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum IndentMode { + Soft, + Block, + SoftSpace, + SoftLineOrSpace, +} + +impl Format for BlockIndent<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let snapshot = f.snapshot(); + + f.write_element(FormatElement::Tag(StartIndent))?; + + match self.mode { + IndentMode::Soft => write!(f, [soft_line_break()])?, + IndentMode::Block => write!(f, [hard_line_break()])?, + IndentMode::SoftLineOrSpace | IndentMode::SoftSpace => { + write!(f, [soft_line_break_or_space()])? + } + } + + let is_empty = { + let mut recording = f.start_recording(); + recording.write_fmt(Arguments::from(&self.content))?; + recording.stop().is_empty() + }; + + if is_empty { + f.restore_snapshot(snapshot); + return Ok(()); + } + + f.write_element(FormatElement::Tag(EndIndent))?; + + match self.mode { + IndentMode::Soft => write!(f, [soft_line_break()]), + IndentMode::Block => write!(f, [hard_line_break()]), + IndentMode::SoftSpace => write!(f, [soft_line_break_or_space()]), + IndentMode::SoftLineOrSpace => Ok(()), + } + } +} + +impl std::fmt::Debug for BlockIndent<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self.mode { + IndentMode::Soft => "SoftBlockIndent", + IndentMode::Block => "HardBlockIndent", + IndentMode::SoftLineOrSpace => "SoftLineIndentOrSpace", + IndentMode::SoftSpace => "SoftSpaceBlockIndent", + }; + + f.debug_tuple(name).field(&"{{content}}").finish() + } +} + +/// Adds spaces around the content if its enclosing group fits on a line, otherwise indents the content and separates it by line breaks. +/// +/// # Examples +/// +/// Adds line breaks and indents the content if the enclosing group doesn't fit on the line. +/// +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(10).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("{"), +/// soft_space_or_block_indent(&format_args![ +/// text("aPropertyThatExceeds"), +/// text(":"), +/// space(), +/// text("'line width'"), +/// ]), +/// text("}") +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "{\n\taPropertyThatExceeds: 'line width'\n}", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// Adds spaces around the content if the group fits on the line +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("{"), +/// soft_space_or_block_indent(&format_args![ +/// text("a"), +/// text(":"), +/// space(), +/// text("5"), +/// ]), +/// text("}") +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "{ a: 5 }", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +pub fn soft_space_or_block_indent(content: &impl Format) -> BlockIndent { + BlockIndent { + content: Argument::new(content), + mode: IndentMode::SoftSpace, + } +} + +/// Creates a logical `Group` around the content that should either consistently be printed on a single line +/// or broken across multiple lines. +/// +/// The printer will try to print the content of the `Group` on a single line, ignoring all soft line breaks and +/// emitting spaces for soft line breaks or spaces. The printer tracks back if it isn't successful either +/// because it encountered a hard line break, or because printing the `Group` on a single line exceeds +/// the configured line width, and thus it must print all its content on multiple lines, +/// emitting line breaks for all line break kinds. +/// +/// # Examples +/// +/// `Group` that fits on a single line +/// +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("1,"), +/// soft_line_break_or_space(), +/// text("2,"), +/// soft_line_break_or_space(), +/// text("3"), +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[1, 2, 3]", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// The printer breaks the `Group` over multiple lines if its content doesn't fit on a single line +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(20).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("'Good morning! How are you today?',"), +/// soft_line_break_or_space(), +/// text("2,"), +/// soft_line_break_or_space(), +/// text("3"), +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[\n\t'Good morning! How are you today?',\n\t2,\n\t3\n]", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn group(content: &impl Format) -> Group { + Group { + content: Argument::new(content), + group_id: None, + should_expand: false, + } +} + +#[derive(Copy, Clone)] +pub struct Group<'a, Context> { + content: Argument<'a, Context>, + group_id: Option, + should_expand: bool, +} + +impl Group<'_, Context> { + pub fn with_group_id(mut self, group_id: Option) -> Self { + self.group_id = group_id; + self + } + + /// Changes the [PrintMode] of the group from [`Flat`](PrintMode::Flat) to [`Expanded`](PrintMode::Expanded). + /// The result is that any soft-line break gets printed as a regular line break. + /// + /// This is useful for content rendered inside of a [FormatElement::BestFitting] that prints each variant + /// in [PrintMode::Flat] to change some content to be printed in [`Expanded`](PrintMode::Expanded) regardless. + /// See the documentation of the [`best_fitting`] macro for an example. + pub fn should_expand(mut self, should_expand: bool) -> Self { + self.should_expand = should_expand; + self + } +} + +impl Format for Group<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let mode = match self.should_expand { + true => GroupMode::Expand, + false => GroupMode::Flat, + }; + + f.write_element(FormatElement::Tag(StartGroup( + tag::Group::new().with_id(self.group_id).with_mode(mode), + )))?; + + Arguments::from(&self.content).fmt(f)?; + + f.write_element(FormatElement::Tag(EndGroup)) + } +} + +impl std::fmt::Debug for Group<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GroupElements") + .field("group_id", &self.group_id) + .field("should_expand", &self.should_expand) + .field("content", &"{{content}}") + .finish() + } +} + +/// IR element that forces the parent group to print in expanded mode. +/// +/// Has no effect if used outside of a group or element that introduce implicit groups (fill element). +/// +/// ## Examples +/// +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("'Good morning! How are you today?',"), +/// soft_line_break_or_space(), +/// text("2,"), +/// expand_parent(), // Forces the parent to expand +/// soft_line_break_or_space(), +/// text("3"), +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[\n\t'Good morning! How are you today?',\n\t2,\n\t3\n]", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// # Prettier +/// Equivalent to Prettier's `break_parent` IR element +pub const fn expand_parent() -> ExpandParent { + ExpandParent +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct ExpandParent; + +impl Format for ExpandParent { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::ExpandParent) + } +} + +/// Adds a conditional content that is emitted only if it isn't inside an enclosing `Group` that +/// is printed on a single line. The element allows, for example, to insert a trailing comma after the last +/// array element only if the array doesn't fit on a single line. +/// +/// The element has no special meaning if used outside of a `Group`. In that case, the content is always emitted. +/// +/// If you're looking for a way to only print something if the `Group` fits on a single line see [self::if_group_fits_on_line]. +/// +/// # Examples +/// +/// Omits the trailing comma for the last array element if the `Group` fits on a single line +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let elements = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("1,"), +/// soft_line_break_or_space(), +/// text("2,"), +/// soft_line_break_or_space(), +/// text("3"), +/// if_group_breaks(&text(",")) +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[1, 2, 3]", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// Prints the trailing comma for the last array element if the `Group` doesn't fit on a single line +/// ``` +/// use ruff_formatter::{format_args, format, LineWidth, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// use ruff_formatter::printer::PrintWidth; +/// +/// fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(20).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let elements = format!(context, [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("'A somewhat longer string to force a line break',"), +/// soft_line_break_or_space(), +/// text("2,"), +/// soft_line_break_or_space(), +/// text("3"), +/// if_group_breaks(&text(",")) +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[\n\t'A somewhat longer string to force a line break',\n\t2,\n\t3,\n]", +/// elements.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn if_group_breaks(content: &Content) -> IfGroupBreaks +where + Content: Format, +{ + IfGroupBreaks { + content: Argument::new(content), + group_id: None, + mode: PrintMode::Expanded, + } +} + +/// Adds a conditional content specific for `Group`s that fit on a single line. The content isn't +/// emitted for `Group`s spanning multiple lines. +/// +/// See [if_group_breaks] if you're looking for a way to print content only for groups spanning multiple lines. +/// +/// # Examples +/// +/// Adds the trailing comma for the last array element if the `Group` fits on a single line +/// ``` +/// use ruff_formatter::{format, format_args}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let formatted = format!(SimpleFormatContext::default(), [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("1,"), +/// soft_line_break_or_space(), +/// text("2,"), +/// soft_line_break_or_space(), +/// text("3"), +/// if_group_fits_on_line(&text(",")) +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[1, 2, 3,]", +/// formatted.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// Omits the trailing comma for the last array element if the `Group` doesn't fit on a single line +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(20).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let formatted = format!(context, [ +/// group(&format_args![ +/// text("["), +/// soft_block_indent(&format_args![ +/// text("'A somewhat longer string to force a line break',"), +/// soft_line_break_or_space(), +/// text("2,"), +/// soft_line_break_or_space(), +/// text("3"), +/// if_group_fits_on_line(&text(",")) +/// ]), +/// text("]"), +/// ]) +/// ])?; +/// +/// assert_eq!( +/// "[\n\t'A somewhat longer string to force a line break',\n\t2,\n\t3\n]", +/// formatted.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn if_group_fits_on_line(flat_content: &Content) -> IfGroupBreaks +where + Content: Format, +{ + IfGroupBreaks { + mode: PrintMode::Flat, + group_id: None, + content: Argument::new(flat_content), + } +} + +#[derive(Copy, Clone)] +pub struct IfGroupBreaks<'a, Context> { + content: Argument<'a, Context>, + group_id: Option, + mode: PrintMode, +} + +impl IfGroupBreaks<'_, Context> { + /// Inserts some content that the printer only prints if the group with the specified `group_id` + /// is printed in multiline mode. The referred group must appear before this element in the document + /// but doesn't have to one of its ancestors. + /// + /// # Examples + /// + /// Prints the trailing comma if the array group doesn't fit. The `group_id` is necessary + /// because `fill` creates an implicit group around each item and tries to print the item in flat mode. + /// The item `[4]` in this example fits on a single line but the trailing comma should still be printed + /// + /// ``` + /// use ruff_formatter::{format, format_args, write, LineWidth, SimpleFormatOptions}; + /// use ruff_formatter::prelude::*; + /// + /// # fn main() -> FormatResult<()> { + /// let context = SimpleFormatContext::new(SimpleFormatOptions { + /// line_width: LineWidth::try_from(20).unwrap(), + /// ..SimpleFormatOptions::default() + /// }); + /// + /// let formatted = format!(context, [format_with(|f| { + /// let group_id = f.group_id("array"); + /// + /// write!(f, [ + /// group( + /// &format_args![ + /// text("["), + /// soft_block_indent(&format_with(|f| { + /// f.fill() + /// .entry(&soft_line_break_or_space(), &text("1,")) + /// .entry(&soft_line_break_or_space(), &text("234568789,")) + /// .entry(&soft_line_break_or_space(), &text("3456789,")) + /// .entry(&soft_line_break_or_space(), &format_args!( + /// text("["), + /// soft_block_indent(&text("4")), + /// text("]"), + /// if_group_breaks(&text(",")).with_group_id(Some(group_id)) + /// )) + /// .finish() + /// })), + /// text("]") + /// ], + /// ).with_group_id(Some(group_id)) + /// ]) + /// })])?; + /// + /// assert_eq!( + /// "[\n\t1, 234568789,\n\t3456789, [4],\n]", + /// formatted.print()?.as_code() + /// ); + /// # Ok(()) + /// # } + /// ``` + pub fn with_group_id(mut self, group_id: Option) -> Self { + self.group_id = group_id; + self + } +} + +impl Format for IfGroupBreaks<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartConditionalContent( + Condition::new(self.mode).with_group_id(self.group_id), + )))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndConditionalContent)) + } +} + +impl std::fmt::Debug for IfGroupBreaks<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = match self.mode { + PrintMode::Flat => "IfGroupFitsOnLine", + PrintMode::Expanded => "IfGroupBreaks", + }; + + f.debug_struct(name) + .field("group_id", &self.group_id) + .field("content", &"{{content}}") + .finish() + } +} + +/// Increases the indent level by one if the group with the specified id breaks. +/// +/// This IR has the same semantics as using [if_group_breaks] and [if_group_fits_on_line] together. +/// +/// ``` +/// # use ruff_formatter::prelude::*; +/// # use ruff_formatter::write; +/// # let format = format_with(|f: &mut Formatter| { +/// let id = f.group_id("head"); +/// +/// write!(f, [ +/// group(&text("Head")).with_group_id(Some(id)), +/// if_group_breaks(&indent(&text("indented"))).with_group_id(Some(id)), +/// if_group_fits_on_line(&text("indented")).with_group_id(Some(id)) +/// ]) +/// +/// # }); +/// ``` +/// +/// If you want to indent some content if the enclosing group breaks, use [`indent`]. +/// +/// Use [if_group_breaks] or [if_group_fits_on_line] if the fitting and breaking content differs more than just the +/// indention level. +/// +/// # Examples +/// +/// Indent the body of an arrow function if the group wrapping the signature breaks: +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let content = format_with(|f| { +/// let group_id = f.group_id("header"); +/// +/// write!(f, [ +/// group(&text("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)), +/// indent_if_group_breaks(&format_args![hard_line_break(), text("a => b")], group_id) +/// ]) +/// }); +/// +/// let context = SimpleFormatContext::new(SimpleFormatOptions { +/// line_width: LineWidth::try_from(20).unwrap(), +/// ..SimpleFormatOptions::default() +/// }); +/// +/// let formatted = format!(context, [content])?; +/// +/// assert_eq!( +/// "(aLongHeaderThatBreaksForSomeReason) =>\n\ta => b", +/// formatted.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +/// +/// It doesn't add an indent if the group wrapping the signature doesn't break: +/// ``` +/// use ruff_formatter::{format, format_args, LineWidth, SimpleFormatOptions, write}; +/// use ruff_formatter::prelude::*; +/// +/// # fn main() -> FormatResult<()> { +/// let content = format_with(|f| { +/// let group_id = f.group_id("header"); +/// +/// write!(f, [ +/// group(&text("(aLongHeaderThatBreaksForSomeReason) =>")).with_group_id(Some(group_id)), +/// indent_if_group_breaks(&format_args![hard_line_break(), text("a => b")], group_id) +/// ]) +/// }); +/// +/// let formatted = format!(SimpleFormatContext::default(), [content])?; +/// +/// assert_eq!( +/// "(aLongHeaderThatBreaksForSomeReason) =>\na => b", +/// formatted.print()?.as_code() +/// ); +/// # Ok(()) +/// # } +/// ``` +#[inline] +pub fn indent_if_group_breaks( + content: &Content, + group_id: GroupId, +) -> IndentIfGroupBreaks +where + Content: Format, +{ + IndentIfGroupBreaks { + group_id, + content: Argument::new(content), + } +} + +#[derive(Copy, Clone)] +pub struct IndentIfGroupBreaks<'a, Context> { + content: Argument<'a, Context>, + group_id: GroupId, +} + +impl Format for IndentIfGroupBreaks<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + f.write_element(FormatElement::Tag(StartIndentIfGroupBreaks(self.group_id)))?; + Arguments::from(&self.content).fmt(f)?; + f.write_element(FormatElement::Tag(EndIndentIfGroupBreaks)) + } +} + +impl std::fmt::Debug for IndentIfGroupBreaks<'_, Context> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IndentIfGroupBreaks") + .field("group_id", &self.group_id) + .field("content", &"{{content}}") + .finish() + } +} + +/// Utility for formatting some content with an inline lambda function. +#[derive(Copy, Clone)] +pub struct FormatWith { + formatter: T, + context: PhantomData, +} + +impl Format for FormatWith +where + T: Fn(&mut Formatter) -> FormatResult<()>, +{ + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + (self.formatter)(f) + } +} + +impl std::fmt::Debug for FormatWith { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("FormatWith").field(&"{{formatter}}").finish() + } +} + +/// Creates an object implementing `Format` that calls the passed closure to perform the formatting. +/// +/// # Examples +/// +/// ``` +/// use ruff_formatter::prelude::*; +/// use ruff_formatter::{SimpleFormatContext, format, write}; +/// use ruff_rowan::TextSize; +/// +/// struct MyFormat { +/// items: Vec<&'static str>, +/// } +/// +/// impl Format for MyFormat { +/// fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { +/// write!(f, [ +/// text("("), +/// block_indent(&format_with(|f| { +/// let separator = space(); +/// let mut join = f.join_with(&separator); +/// +/// for item in &self.items { +/// join.entry(&format_with(|f| write!(f, [dynamic_text(item, TextSize::default())]))); +/// } +/// join.finish() +/// })), +/// text(")") +/// ]) +/// } +/// } +/// +/// # fn main() -> FormatResult<()> { +/// let formatted = format!(SimpleFormatContext::default(), [MyFormat { items: vec!["a", "b", "c"]}])?; +/// +/// assert_eq!("(\n\ta b c\n)", formatted.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +pub const fn format_with(formatter: T) -> FormatWith +where + T: Fn(&mut Formatter) -> FormatResult<()>, +{ + FormatWith { + formatter, + context: PhantomData, + } +} + +/// Creates an inline `Format` object that can only be formatted once. +/// +/// This can be useful in situation where the borrow checker doesn't allow you to use [`format_with`] +/// because the code formatting the content consumes the value and cloning the value is too expensive. +/// An example of this is if you want to nest a `FormatElement` or non-cloneable `Iterator` inside of a +/// `block_indent` as shown can see in the examples section. +/// +/// # Panics +/// +/// Panics if the object gets formatted more than once. +/// +/// # Example +/// +/// ``` +/// use ruff_formatter::prelude::*; +/// use ruff_formatter::{SimpleFormatContext, format, write, Buffer}; +/// +/// struct MyFormat; +/// +/// fn generate_values() -> impl Iterator { +/// vec![text("1"), text("2"), text("3"), text("4")].into_iter() +/// } +/// +/// impl Format for MyFormat { +/// fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { +/// let mut values = generate_values(); +/// +/// let first = values.next(); +/// +/// // Formats the first item outside of the block and all other items inside of the block, +/// // separated by line breaks +/// write!(f, [ +/// first, +/// block_indent(&format_once(|f| { +/// // Using format_with isn't possible here because the iterator gets consumed here +/// f.join_with(&hard_line_break()).entries(values).finish() +/// })), +/// ]) +/// } +/// } +/// +/// # fn main() -> FormatResult<()> { +/// let formatted = format!(SimpleFormatContext::default(), [MyFormat])?; +/// +/// assert_eq!("1\n\t2\n\t3\n\t4\n", formatted.print()?.as_code()); +/// # Ok(()) +/// # } +/// ``` +/// +/// Formatting the same value twice results in a panic. +/// +/// ```panics +/// use ruff_formatter::prelude::*; +/// use ruff_formatter::{SimpleFormatContext, format, write, Buffer}; +/// use ruff_rowan::TextSize; +/// +/// let mut count = 0; +/// +/// let value = format_once(|f| { +/// write!(f, [dynamic_token(&std::format!("Formatted {count}."), TextSize::default())]) +/// }); +/// +/// format!(SimpleFormatContext::default(), [value]).expect("Formatting once works fine"); +/// +/// // Formatting the value more than once panics +/// format!(SimpleFormatContext::default(), [value]); +/// ``` +pub const fn format_once(formatter: T) -> FormatOnce +where + T: FnOnce(&mut Formatter) -> FormatResult<()>, +{ + FormatOnce { + formatter: Cell::new(Some(formatter)), + context: PhantomData, + } +} + +pub struct FormatOnce { + formatter: Cell>, + context: PhantomData, +} + +impl Format for FormatOnce +where + T: FnOnce(&mut Formatter) -> FormatResult<()>, +{ + #[inline(always)] + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let formatter = self.formatter.take().expect("Tried to format a `format_once` at least twice. This is not allowed. You may want to use `format_with` or `format.memoized` instead."); + + (formatter)(f) + } +} + +impl std::fmt::Debug for FormatOnce { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("FormatOnce").field(&"{{formatter}}").finish() + } +} + +/// Builder to join together a sequence of content. +/// See [Formatter::join] +#[must_use = "must eventually call `finish()` on Format builders"] +pub struct JoinBuilder<'fmt, 'buf, Separator, Context> { + result: FormatResult<()>, + fmt: &'fmt mut Formatter<'buf, Context>, + with: Option, + has_elements: bool, +} + +impl<'fmt, 'buf, Separator, Context> JoinBuilder<'fmt, 'buf, Separator, Context> +where + Separator: Format, +{ + /// Creates a new instance that joins the elements without a separator + pub(super) fn new(fmt: &'fmt mut Formatter<'buf, Context>) -> Self { + Self { + result: Ok(()), + fmt, + has_elements: false, + with: None, + } + } + + /// Creates a new instance that prints the passed separator between every two entries. + pub(super) fn with_separator(fmt: &'fmt mut Formatter<'buf, Context>, with: Separator) -> Self { + Self { + result: Ok(()), + fmt, + has_elements: false, + with: Some(with), + } + } + + /// Adds a new entry to the join output. + pub fn entry(&mut self, entry: &dyn Format) -> &mut Self { + self.result = self.result.and_then(|_| { + if let Some(with) = &self.with { + if self.has_elements { + with.fmt(self.fmt)?; + } + } + self.has_elements = true; + + entry.fmt(self.fmt) + }); + + self + } + + /// Adds the contents of an iterator of entries to the join output. + pub fn entries(&mut self, entries: I) -> &mut Self + where + F: Format, + I: IntoIterator, + { + for entry in entries { + self.entry(&entry); + } + + self + } + + /// Finishes the output and returns any error encountered. + pub fn finish(&mut self) -> FormatResult<()> { + self.result + } +} + +/// Builder to join together nodes that ensures that nodes separated by empty lines continue +/// to be separated by empty lines in the formatted output. +#[must_use = "must eventually call `finish()` on Format builders"] +pub struct JoinNodesBuilder<'fmt, 'buf, Separator, Context> { + result: FormatResult<()>, + /// The separator to insert between nodes. Either a soft or hard line break + separator: Separator, + fmt: &'fmt mut Formatter<'buf, Context>, + has_elements: bool, +} + +impl<'fmt, 'buf, Separator, Context> JoinNodesBuilder<'fmt, 'buf, Separator, Context> +where + Separator: Format, +{ + pub(super) fn new(separator: Separator, fmt: &'fmt mut Formatter<'buf, Context>) -> Self { + Self { + result: Ok(()), + separator, + fmt, + has_elements: false, + } + } + + /// Adds a new node with the specified formatted content to the output, respecting any new lines + /// that appear before the node in the input source. + pub fn entry(&mut self, node: &SyntaxNode, content: &dyn Format) { + self.result = self.result.and_then(|_| { + if self.has_elements { + if get_lines_before(node) > 1 { + write!(self.fmt, [empty_line()])?; + } else { + self.separator.fmt(self.fmt)?; + } + } + + self.has_elements = true; + + write!(self.fmt, [content]) + }); + } + + /// Writes an entry without adding a separating line break or empty line. + pub fn entry_no_separator(&mut self, content: &dyn Format) { + self.result = self.result.and_then(|_| { + self.has_elements = true; + + write!(self.fmt, [content]) + }) + } + + /// Adds an iterator of entries to the output. Each entry is a `(node, content)` tuple. + pub fn entries(&mut self, entries: I) -> &mut Self + where + L: Language, + F: Format, + I: IntoIterator, F)>, + { + for (node, content) in entries { + self.entry(&node, &content) + } + + self + } + + pub fn finish(&mut self) -> FormatResult<()> { + self.result + } +} + +/// Get the number of line breaks between two consecutive SyntaxNodes in the tree +pub fn get_lines_before(next_node: &SyntaxNode) -> usize { + // Count the newlines in the leading trivia of the next node + if let Some(token) = next_node.first_token() { + get_lines_before_token(&token) + } else { + 0 + } +} + +pub fn get_lines_before_token(token: &SyntaxToken) -> usize { + token + .leading_trivia() + .pieces() + .take_while(|piece| { + // Stop at the first comment or skipped piece, the comment printer + // will handle newlines between the comment and the node + !(piece.is_comments() || piece.is_skipped()) + }) + .filter(|piece| piece.is_newline()) + .count() +} + +/// Builder to fill as many elements as possible on a single line. +#[must_use = "must eventually call `finish()` on Format builders"] +pub struct FillBuilder<'fmt, 'buf, Context> { + result: FormatResult<()>, + fmt: &'fmt mut Formatter<'buf, Context>, + empty: bool, +} + +impl<'a, 'buf, Context> FillBuilder<'a, 'buf, Context> { + pub(crate) fn new(fmt: &'a mut Formatter<'buf, Context>) -> Self { + let result = fmt.write_element(FormatElement::Tag(StartFill)); + + Self { + result, + fmt, + empty: true, + } + } + + /// Adds an iterator of entries to the fill output. Uses the passed `separator` to separate any two items. + pub fn entries(&mut self, separator: &dyn Format, entries: I) -> &mut Self + where + F: Format, + I: IntoIterator, + { + for entry in entries { + self.entry(separator, &entry); + } + + self + } + + /// Adds a new entry to the fill output. The `separator` isn't written if this is the first element in the list. + pub fn entry( + &mut self, + separator: &dyn Format, + entry: &dyn Format, + ) -> &mut Self { + self.result = self.result.and_then(|_| { + if self.empty { + self.empty = false; + } else { + self.fmt.write_element(FormatElement::Tag(StartEntry))?; + separator.fmt(self.fmt)?; + self.fmt.write_element(FormatElement::Tag(EndEntry))?; + } + + self.fmt.write_element(FormatElement::Tag(StartEntry))?; + entry.fmt(self.fmt)?; + self.fmt.write_element(FormatElement::Tag(EndEntry)) + }); + + self + } + + /// Finishes the output and returns any error encountered + pub fn finish(&mut self) -> FormatResult<()> { + self.result + .and_then(|_| self.fmt.write_element(FormatElement::Tag(EndFill))) + } +} + +/// The first variant is the most flat, and the last is the most expanded variant. +/// See [`best_fitting!`] macro for a more in-detail documentation +#[derive(Copy, Clone)] +pub struct BestFitting<'a, Context> { + variants: Arguments<'a, Context>, +} + +impl<'a, Context> BestFitting<'a, Context> { + /// Creates a new best fitting IR with the given variants. The method itself isn't unsafe + /// but it is to discourage people from using it because the printer will panic if + /// the slice doesn't contain at least the least and most expanded variants. + /// + /// You're looking for a way to create a `BestFitting` object, use the `best_fitting![least_expanded, most_expanded]` macro. + /// + /// ## Safety + /// The slice must contain at least two variants. + pub unsafe fn from_arguments_unchecked(variants: Arguments<'a, Context>) -> Self { + assert!( + variants.0.len() >= 2, + "Requires at least the least expanded and most expanded variants" + ); + + Self { variants } + } +} + +impl Format for BestFitting<'_, Context> { + fn fmt(&self, f: &mut Formatter) -> FormatResult<()> { + let mut buffer = VecBuffer::new(f.state_mut()); + let variants = self.variants.items(); + + let mut formatted_variants = Vec::with_capacity(variants.len()); + + for variant in variants { + buffer.write_element(FormatElement::Tag(StartEntry))?; + buffer.write_fmt(Arguments::from(variant))?; + buffer.write_element(FormatElement::Tag(EndEntry))?; + + formatted_variants.push(buffer.take_vec().into_boxed_slice()); + } + + // SAFETY: The constructor guarantees that there are always at least two variants. It's, therefore, + // safe to call into the unsafe `from_vec_unchecked` function + let element = unsafe { + FormatElement::BestFitting(format_element::BestFitting::from_vec_unchecked( + formatted_variants, + )) + }; + + f.write_element(element) + } +} diff --git a/crates/ruff_formatter/src/comments.rs b/crates/ruff_formatter/src/comments.rs new file mode 100644 index 0000000000..861b4d6b9b --- /dev/null +++ b/crates/ruff_formatter/src/comments.rs @@ -0,0 +1,1175 @@ +//! Types for extracting and representing comments of a syntax tree. +//! +//! Most programming languages support comments allowing programmers to document their programs. Comments are different from other syntaxes because programming languages allow comments in almost any position, giving programmers great flexibility on where they can write comments: +//! +//! ```ignore +//! /** +//! * Documentation comment +//! */ +//! async /* comment */ function Test () // line comment +//! {/*inline*/} +//! ``` +//! +//! However, this flexibility makes formatting comments challenging because: +//! * The formatter must consistently place comments so that re-formatting the output yields the same result and does not create invalid syntax (line comments). +//! * It is essential that formatters place comments close to the syntax the programmer intended to document. However, the lack of rules regarding where comments are allowed and what syntax they document requires the use of heuristics to infer the documented syntax. +//! +//! This module strikes a balance between placing comments as closely as possible to their source location and reducing the complexity of formatting comments. It does so by associating comments per node rather than a token. This greatly reduces the combinations of possible comment positions but turns out to be, in practice, sufficiently precise to keep comments close to their source location. +//! +//! ## Node comments +//! +//! Comments are associated per node but get further distinguished on their location related to that node: +//! +//! ### Leading Comments +//! +//! A comment at the start of a node +//! +//! ```ignore +//! // Leading comment of the statement +//! console.log("test"); +//! +//! [/* leading comment of identifier */ a ]; +//! ``` +//! +//! ### Dangling Comments +//! +//! A comment that is neither at the start nor the end of a node +//! +//! ```ignore +//! [/* in between the brackets */ ]; +//! async /* between keywords */ function Test () {} +//! ``` +//! +//! ### Trailing Comments +//! +//! A comment at the end of a node +//! +//! ```ignore +//! [a /* trailing comment of a */, b, c]; +//! [ +//! a // trailing comment of a +//! ] +//! ``` +//! +//! ## Limitations +//! Limiting the placement of comments to leading, dangling, or trailing node comments reduces complexity inside the formatter but means, that the formatter's possibility of where comments can be formatted depends on the AST structure. +//! +//! For example, the continue statement in JavaScript is defined as: +//! +//! ```ungram +//! JsContinueStatement = +//! 'continue' +//! (label: 'ident')? +//! ';'? +//! ``` +//! +//! but a programmer may decide to add a comment in front or after the label: +//! +//! ```ignore +//! continue /* comment 1 */ label; +//! continue label /* comment 2*/; /* trailing */ +//! ``` +//! +//! Because all children of the `continue` statement are tokens, it is only possible to make the comments leading, dangling, or trailing comments of the `continue` statement. But this results in a loss of information as the formatting code can no longer distinguish if a comment appeared before or after the label and, thus, has to format them the same way. +//! +//! This hasn't shown to be a significant limitation today but the infrastructure could be extended to support a `label` on [`SourceComment`] that allows to further categorise comments. +//! + +mod builder; +mod map; + +use self::{builder::CommentsBuilderVisitor, map::CommentsMap}; +use crate::formatter::Formatter; +use crate::{buffer::Buffer, write}; +use crate::{CstFormatContext, FormatResult, FormatRule, TextSize, TransformSourceMap}; +use ruff_rowan::syntax::SyntaxElementKey; +use ruff_rowan::{Language, SyntaxNode, SyntaxToken, SyntaxTriviaPieceComments}; +use rustc_hash::FxHashSet; +#[cfg(debug_assertions)] +use std::cell::{Cell, RefCell}; +use std::marker::PhantomData; +use std::rc::Rc; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum CommentKind { + /// An inline comment that can appear between any two tokens and doesn't contain any line breaks. + /// + /// ## Examples + /// + /// ```ignore + /// a /* test */ + /// ``` + InlineBlock, + + /// A block comment that can appear between any two tokens and contains at least one line break. + /// + /// ## Examples + /// + /// ```javascript + /// /* first line + /// * more content on the second line + /// */ + /// ``` + Block, + + /// A line comment that appears at the end of the line. + /// + /// ## Examples + /// + /// ```ignore + /// a // test + /// ``` + Line, +} + +impl CommentKind { + pub const fn is_line(&self) -> bool { + matches!(self, CommentKind::Line) + } + + pub const fn is_block(&self) -> bool { + matches!(self, CommentKind::Block) + } + + pub const fn is_inline_block(&self) -> bool { + matches!(self, CommentKind::InlineBlock) + } + + /// Returns `true` for comments that can appear inline between any two tokens. + /// + /// ## Examples + /// + /// ```rust + /// use ruff_formatter::comments::CommentKind; + /// + /// // Block and InlineBlock comments can appear inline + /// assert!(CommentKind::Block.is_inline()); + /// assert!(CommentKind::InlineBlock.is_inline()); + /// + /// // But not line comments + /// assert!(!CommentKind::Line.is_inline()) + /// ``` + pub const fn is_inline(&self) -> bool { + matches!(self, CommentKind::InlineBlock | CommentKind::Block) + } +} + +/// A comment in the source document. +#[derive(Debug, Clone)] +pub struct SourceComment { + /// The number of lines appearing before this comment + pub(crate) lines_before: u32, + + pub(crate) lines_after: u32, + + /// The comment piece + pub(crate) piece: SyntaxTriviaPieceComments, + + /// The kind of the comment. + pub(crate) kind: CommentKind, + + /// Whether the comment has been formatted or not. + #[cfg(debug_assertions)] + pub(crate) formatted: Cell, +} + +impl SourceComment { + /// Returns the underlining comment trivia piece + pub fn piece(&self) -> &SyntaxTriviaPieceComments { + &self.piece + } + + /// The number of lines between this comment and the **previous** token or comment. + /// + /// # Examples + /// + /// ## Same line + /// + /// ```ignore + /// a // end of line + /// ``` + /// + /// Returns `0` because there's no line break between the token `a` and the comment. + /// + /// ## Own Line + /// + /// ```ignore + /// a; + /// + /// /* comment */ + /// ``` + /// + /// Returns `2` because there are two line breaks between the token `a` and the comment. + pub fn lines_before(&self) -> u32 { + self.lines_before + } + + /// The number of line breaks right after this comment. + /// + /// # Examples + /// + /// ## End of line + /// + /// ```ignore + /// a; // comment + /// + /// b; + /// ``` + /// + /// Returns `2` because there are two line breaks between the comment and the token `b`. + /// + /// ## Same line + /// + /// ```ignore + /// a; + /// /* comment */ b; + /// ``` + /// + /// Returns `0` because there are no line breaks between the comment and the token `b`. + pub fn lines_after(&self) -> u32 { + self.lines_after + } + + /// The kind of the comment + pub fn kind(&self) -> CommentKind { + self.kind + } + + #[cfg(not(debug_assertions))] + #[inline(always)] + pub fn mark_formatted(&self) {} + + /// Marks the comment as formatted + #[cfg(debug_assertions)] + pub fn mark_formatted(&self) { + self.formatted.set(true) + } +} + +/// A comment decorated with additional information about its surrounding context in the source document. +/// +/// Used by [CommentStyle::place_comment] to determine if this should become a [leading](self#leading-comments), [dangling](self#dangling-comments), or [trailing](self#trailing-comments) comment. +#[derive(Debug, Clone)] +pub struct DecoratedComment { + enclosing: SyntaxNode, + preceding: Option>, + following: Option>, + following_token: Option>, + text_position: CommentTextPosition, + lines_before: u32, + lines_after: u32, + comment: SyntaxTriviaPieceComments, + kind: CommentKind, +} + +impl DecoratedComment { + /// The closest parent node that fully encloses the comment. + /// + /// A node encloses a comment when the comment is between two of its direct children (ignoring lists). + /// + /// # Examples + /// + /// ```ignore + /// [a, /* comment */ b] + /// ``` + /// + /// The enclosing node is the array expression and not the identifier `b` because + /// `a` and `b` are children of the array expression and `comment` is a comment between the two nodes. + pub fn enclosing_node(&self) -> &SyntaxNode { + &self.enclosing + } + + /// Returns the comment piece. + pub fn piece(&self) -> &SyntaxTriviaPieceComments { + &self.comment + } + + /// Returns the node preceding the comment. + /// + /// The direct child node (ignoring lists) of the [`enclosing_node`](DecoratedComment::enclosing_node) that precedes this comment. + /// + /// Returns [None] if the [`enclosing_node`](DecoratedComment::enclosing_node) only consists of tokens or if + /// all preceding children of the [`enclosing_node`](DecoratedComment::enclosing_node) have been tokens. + /// + /// The Preceding node is guaranteed to be a sibling of [`following_node`](DecoratedComment::following_node). + /// + /// # Examples + /// + /// ## Preceding tokens only + /// + /// ```ignore + /// [/* comment */] + /// ``` + /// Returns [None] because the comment has no preceding node, only a preceding `[` token. + /// + /// ## Preceding node + /// + /// ```ignore + /// [a /* comment */, b] + /// ``` + /// + /// Returns `Some(a)` because `a` directly precedes the comment. + /// + /// ## Preceding token and node + /// + /// ```ignore + /// [a, /* comment */] + /// ``` + /// + /// Returns `Some(a)` because `a` is the preceding node of `comment`. The presence of the `,` token + /// doesn't change that. + pub fn preceding_node(&self) -> Option<&SyntaxNode> { + self.preceding.as_ref() + } + + /// Takes the [`preceding_node`](DecoratedComment::preceding_node) and replaces it with [None]. + fn take_preceding_node(&mut self) -> Option> { + self.preceding.take() + } + + /// Returns the node following the comment. + /// + /// The direct child node (ignoring lists) of the [`enclosing_node`](DecoratedComment::enclosing_node) that follows this comment. + /// + /// Returns [None] if the [`enclosing_node`](DecoratedComment::enclosing_node) only consists of tokens or if + /// all children children of the [`enclosing_node`](DecoratedComment::enclosing_node) following this comment are tokens. + /// + /// The following node is guaranteed to be a sibling of [`preceding_node`](DecoratedComment::preceding_node). + /// + /// # Examples + /// + /// ## Following tokens only + /// + /// ```ignore + /// [ /* comment */ ] + /// ``` + /// + /// Returns [None] because there's no node following the comment, only the `]` token. + /// + /// ## Following node + /// + /// ```ignore + /// [ /* comment */ a ] + /// ``` + /// + /// Returns `Some(a)` because `a` is the node directly following the comment. + /// + /// ## Following token and node + /// + /// ```ignore + /// async /* comment */ function test() {} + /// ``` + /// + /// Returns `Some(test)` because the `test` identifier is the first node following `comment`. + /// + /// ## Following parenthesized expression + /// + /// ```ignore + /// !( + /// a /* comment */ + /// ); + /// b + /// ``` + /// + /// Returns `None` because `comment` is enclosed inside the parenthesized expression and it has no children + /// following `/* comment */. + pub fn following_node(&self) -> Option<&SyntaxNode> { + self.following.as_ref() + } + + /// Takes the [`following_node`](DecoratedComment::following_node) and replaces it with [None]. + fn take_following_node(&mut self) -> Option> { + self.following.take() + } + + /// The number of line breaks between this comment and the **previous** token or comment. + /// + /// # Examples + /// + /// ## Same line + /// + /// ```ignore + /// a // end of line + /// ``` + /// + /// Returns `0` because there's no line break between the token `a` and the comment. + /// + /// ## Own Line + /// + /// ```ignore + /// a; + /// + /// /* comment */ + /// ``` + /// + /// Returns `2` because there are two line breaks between the token `a` and the comment. + pub fn lines_before(&self) -> u32 { + self.lines_before + } + + /// The number of line breaks right after this comment. + /// + /// # Examples + /// + /// ## End of line + /// + /// ```ignore + /// a; // comment + /// + /// b; + /// ``` + /// + /// Returns `2` because there are two line breaks between the comment and the token `b`. + /// + /// ## Same line + /// + /// ```ignore + /// a; + /// /* comment */ b; + /// ``` + /// + /// Returns `0` because there are no line breaks between the comment and the token `b`. + pub fn lines_after(&self) -> u32 { + self.lines_after + } + + /// Returns the [CommentKind] of the comment. + pub fn kind(&self) -> CommentKind { + self.kind + } + + /// The position of the comment in the text. + pub fn text_position(&self) -> CommentTextPosition { + self.text_position + } + + /// The next token that comes after this comment. It is possible that other comments are between this comment + /// and the token. + /// + /// ```ignore + /// a /* comment */ /* other b */ + /// ``` + /// + /// The `following_token` for both comments is `b` because it's the token coming after the comments. + pub fn following_token(&self) -> Option<&SyntaxToken> { + self.following_token.as_ref() + } +} + +impl From> for SourceComment { + fn from(decorated: DecoratedComment) -> Self { + Self { + lines_before: decorated.lines_before, + lines_after: decorated.lines_after, + piece: decorated.comment, + kind: decorated.kind, + #[cfg(debug_assertions)] + formatted: Cell::new(false), + } + } +} + +/// The position of a comment in the source text. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum CommentTextPosition { + /// A comment that is on the same line as the preceding token and is separated by at least one line break from the following token. + /// + /// # Examples + /// + /// ## End of line + /// + /// ```ignore + /// a; /* this */ // or this + /// b; + /// ``` + /// + /// Both `/* this */` and `// or this` are end of line comments because both comments are separated by + /// at least one line break from the following token `b`. + /// + /// ## Own line + /// + /// ```ignore + /// a; + /// /* comment */ + /// b; + /// ``` + /// + /// This is not an end of line comment because it isn't on the same line as the preceding token `a`. + EndOfLine, + + /// A Comment that is separated by at least one line break from the preceding token. + /// + /// # Examples + /// + /// ```ignore + /// a; + /// /* comment */ /* or this */ + /// b; + /// ``` + /// + /// Both comments are own line comments because they are separated by one line break from the preceding + /// token `a`. + OwnLine, + + /// A comment that is placed on the same line as the preceding and following token. + /// + /// # Examples + /// + /// ```ignore + /// a /* comment */ + b + /// ``` + SameLine, +} + +impl CommentTextPosition { + pub const fn is_same_line(&self) -> bool { + matches!(self, CommentTextPosition::SameLine) + } + + pub const fn is_own_line(&self) -> bool { + matches!(self, CommentTextPosition::OwnLine) + } + + pub const fn is_end_of_line(&self) -> bool { + matches!(self, CommentTextPosition::EndOfLine) + } +} + +#[derive(Debug)] +pub enum CommentPlacement { + /// Makes `comment` a [leading comment](self#leading-comments) of `node`. + Leading { + node: SyntaxNode, + comment: SourceComment, + }, + /// Makes `comment` a [trailing comment](self#trailing-comments) of `node`. + Trailing { + node: SyntaxNode, + comment: SourceComment, + }, + + /// Makes `comment` a [dangling comment](self#dangling-comments) of `node`. + Dangling { + node: SyntaxNode, + comment: SourceComment, + }, + + /// Uses the default heuristic to determine the placement of the comment. + /// + /// # Same line comments + /// + /// Makes the comment a... + /// + /// * [trailing comment] of the [`preceding_node`] if both the [`following_node`] and [`preceding_node`] are not [None] + /// and the comment and [`preceding_node`] are only separated by a space (there's no token between the comment and [`preceding_node`]). + /// * [leading comment] of the [`following_node`] if the [`following_node`] is not [None] + /// * [trailing comment] of the [`preceding_node`] if the [`preceding_node`] is not [None] + /// * [dangling comment] of the [`enclosing_node`]. + /// + /// ## Examples + /// ### Comment with preceding and following nodes + /// + /// ```ignore + /// [ + /// a, // comment + /// b + /// ] + /// ``` + /// + /// The comment becomes a [trailing comment] of the node `a`. + /// + /// ### Comment with preceding node only + /// + /// ```ignore + /// [ + /// a // comment + /// ] + /// ``` + /// + /// The comment becomes a [trailing comment] of the node `a`. + /// + /// ### Comment with following node only + /// + /// ```ignore + /// [ // comment + /// b + /// ] + /// ``` + /// + /// The comment becomes a [leading comment] of the node `b`. + /// + /// ### Dangling comment + /// + /// ```ignore + /// [ // comment + /// ] + /// ``` + /// + /// The comment becomes a [dangling comment] of the enclosing array expression because both the [`preceding_node`] and [`following_node`] are [None]. + /// + /// # Own line comments + /// + /// Makes the comment a... + /// + /// * [leading comment] of the [`following_node`] if the [`following_node`] is not [None] + /// * or a [trailing comment] of the [`preceding_node`] if the [`preceding_node`] is not [None] + /// * or a [dangling comment] of the [`enclosing_node`]. + /// + /// ## Examples + /// + /// ### Comment with leading and preceding nodes + /// + /// ```ignore + /// [ + /// a, + /// // comment + /// b + /// ] + /// ``` + /// + /// The comment becomes a [leading comment] of the node `b`. + /// + /// ### Comment with preceding node only + /// + /// ```ignore + /// [ + /// a + /// // comment + /// ] + /// ``` + /// + /// The comment becomes a [trailing comment] of the node `a`. + /// + /// ### Comment with following node only + /// + /// ```ignore + /// [ + /// // comment + /// b + /// ] + /// ``` + /// + /// The comment becomes a [leading comment] of the node `b`. + /// + /// ### Dangling comment + /// + /// ```ignore + /// [ + /// // comment + /// ] + /// ``` + /// + /// The comment becomes a [dangling comment] of the array expression because both [`preceding_node`] and [`following_node`] are [None]. + /// + /// + /// # End of line comments + /// Makes the comment a... + /// + /// * [trailing comment] of the [`preceding_node`] if the [`preceding_node`] is not [None] + /// * or a [leading comment] of the [`following_node`] if the [`following_node`] is not [None] + /// * or a [dangling comment] of the [`enclosing_node`]. + /// + /// + /// ## Examples + /// + /// ### Comment with leading and preceding nodes + /// + /// ```ignore + /// [a /* comment */, b] + /// ``` + /// + /// The comment becomes a [trailing comment] of the node `a` because there's no token between the node `a` and the `comment`. + /// + /// ```ignore + /// [a, /* comment */ b] + /// ``` + /// + /// The comment becomes a [leading comment] of the node `b` because the node `a` and the comment are separated by a `,` token. + /// + /// ### Comment with preceding node only + /// + /// ```ignore + /// [a, /* last */ ] + /// ``` + /// + /// The comment becomes a [trailing comment] of the node `a` because the [`following_node`] is [None]. + /// + /// ### Comment with following node only + /// + /// ```ignore + /// [/* comment */ b] + /// ``` + /// + /// The comment becomes a [leading comment] of the node `b` because the [`preceding_node`] is [None] + /// + /// ### Dangling comment + /// + /// ```ignore + /// [/* comment*/] + /// ``` + /// + /// The comment becomes a [dangling comment] of the array expression because both [`preceding_node`] and [`following_node`] are [None]. + /// + /// [`preceding_node`]: DecoratedComment::preceding_node + /// [`following_node`]: DecoratedComment::following_node + /// [`enclosing_node`]: DecoratedComment::enclosing_node + /// [trailing comment]: self#trailing-comments + /// [leading comment]: self#leading-comments + /// [dangling comment]: self#dangling-comments + Default(DecoratedComment), +} + +impl CommentPlacement { + /// Makes `comment` a [leading comment](self#leading-comments) of `node`. + #[inline] + pub fn leading(node: SyntaxNode, comment: impl Into>) -> Self { + Self::Leading { + node, + comment: comment.into(), + } + } + + /// Makes `comment` a [dangling comment](self::dangling-comments) of `node`. + pub fn dangling(node: SyntaxNode, comment: impl Into>) -> Self { + Self::Dangling { + node, + comment: comment.into(), + } + } + + /// Makes `comment` a [trailing comment](self::trailing-comments) of `node`. + #[inline] + pub fn trailing(node: SyntaxNode, comment: impl Into>) -> Self { + Self::Trailing { + node, + comment: comment.into(), + } + } + + /// Returns the placement if it isn't [CommentPlacement::Default], otherwise calls `f` and returns the result. + #[inline] + pub fn or_else(self, f: F) -> Self + where + F: FnOnce(DecoratedComment) -> CommentPlacement, + { + match self { + CommentPlacement::Default(comment) => f(comment), + placement => placement, + } + } +} + +/// Defines how to format comments for a specific [Language]. +pub trait CommentStyle: Default { + type Language: Language; + + /// Returns `true` if a comment with the given `text` is a `rome-ignore format:` suppression comment. + fn is_suppression(_text: &str) -> bool { + false + } + + /// Returns the (kind)[CommentKind] of the comment + fn get_comment_kind(comment: &SyntaxTriviaPieceComments) -> CommentKind; + + /// Determines the placement of `comment`. + /// + /// The default implementation returns [CommentPlacement::Default]. + fn place_comment( + &self, + comment: DecoratedComment, + ) -> CommentPlacement { + CommentPlacement::Default(comment) + } +} + +/// The comments of a syntax tree stored by node. +/// +/// Cloning `comments` is cheap as it only involves bumping a reference counter. +#[derive(Debug, Clone, Default)] +pub struct Comments { + /// The use of a [Rc] is necessary to achieve that [Comments] has a lifetime that is independent from the [crate::Formatter]. + /// Having independent lifetimes is necessary to support the use case where a (formattable object)[crate::Format] + /// iterates over all comments, and writes them into the [crate::Formatter] (mutably borrowing the [crate::Formatter] and in turn its context). + /// + /// ```block + /// for leading in f.context().comments().leading_comments(node) { + /// ^ + /// |- Borrows comments + /// write!(f, [comment(leading.piece.text())])?; + /// ^ + /// |- Mutably borrows the formatter, state, context, and comments (if comments aren't cloned) + /// } + /// ``` + /// + /// Using an `Rc` here allows to cheaply clone [Comments] for these use cases. + data: Rc>, +} + +impl Comments { + /// Extracts all the comments from `root` and its descendants nodes. + pub fn from_node