mirror of https://github.com/astral-sh/ruff
Range formatting: Fix invalid syntax after parenthesizing expression (#9751)
This commit is contained in:
parent
50bfbcf568
commit
4f7fb566f0
|
|
@ -308,11 +308,8 @@ impl std::fmt::Debug for Token {
|
||||||
/// assert_eq!(printed.as_code(), r#""Hello 'Ruff'""#);
|
/// assert_eq!(printed.as_code(), r#""Hello 'Ruff'""#);
|
||||||
/// assert_eq!(printed.sourcemap(), [
|
/// assert_eq!(printed.sourcemap(), [
|
||||||
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(0) },
|
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(0) },
|
||||||
/// SourceMarker { source: TextSize::new(0), dest: TextSize::new(7) },
|
|
||||||
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(7) },
|
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(7) },
|
||||||
/// SourceMarker { source: TextSize::new(8), dest: TextSize::new(13) },
|
|
||||||
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(13) },
|
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(13) },
|
||||||
/// SourceMarker { source: TextSize::new(14), dest: TextSize::new(14) },
|
|
||||||
/// SourceMarker { source: TextSize::new(20), dest: TextSize::new(14) },
|
/// SourceMarker { source: TextSize::new(20), dest: TextSize::new(14) },
|
||||||
/// ]);
|
/// ]);
|
||||||
///
|
///
|
||||||
|
|
@ -340,18 +337,18 @@ impl<Context> Format<Context> for SourcePosition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a text from a dynamic string with its optional start-position in the source document.
|
/// Creates a text from a dynamic string.
|
||||||
|
///
|
||||||
/// This is done by allocating a new string internally.
|
/// This is done by allocating a new string internally.
|
||||||
pub fn text(text: &str, position: Option<TextSize>) -> Text {
|
pub fn text(text: &str) -> Text {
|
||||||
debug_assert_no_newlines(text);
|
debug_assert_no_newlines(text);
|
||||||
|
|
||||||
Text { text, position }
|
Text { text }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq)]
|
#[derive(Eq, PartialEq)]
|
||||||
pub struct Text<'a> {
|
pub struct Text<'a> {
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
position: Option<TextSize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Context> Format<Context> for Text<'_>
|
impl<Context> Format<Context> for Text<'_>
|
||||||
|
|
@ -359,10 +356,6 @@ where
|
||||||
Context: FormatContext,
|
Context: FormatContext,
|
||||||
{
|
{
|
||||||
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
fn fmt(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
|
||||||
if let Some(position) = self.position {
|
|
||||||
source_position(position).fmt(f)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_element(FormatElement::Text {
|
f.write_element(FormatElement::Text {
|
||||||
text: self.text.to_string().into_boxed_str(),
|
text: self.text.to_string().into_boxed_str(),
|
||||||
text_width: TextWidth::from_text(self.text, f.options().indent_width()),
|
text_width: TextWidth::from_text(self.text, f.options().indent_width()),
|
||||||
|
|
@ -2292,7 +2285,7 @@ impl<Context, T> std::fmt::Debug for FormatWith<Context, T> {
|
||||||
/// let mut join = f.join_with(&separator);
|
/// let mut join = f.join_with(&separator);
|
||||||
///
|
///
|
||||||
/// for item in &self.items {
|
/// for item in &self.items {
|
||||||
/// join.entry(&format_with(|f| write!(f, [text(item, None)])));
|
/// join.entry(&format_with(|f| write!(f, [text(item)])));
|
||||||
/// }
|
/// }
|
||||||
/// join.finish()
|
/// join.finish()
|
||||||
/// })),
|
/// })),
|
||||||
|
|
@ -2377,7 +2370,7 @@ where
|
||||||
/// let mut count = 0;
|
/// let mut count = 0;
|
||||||
///
|
///
|
||||||
/// let value = format_once(|f| {
|
/// let value = format_once(|f| {
|
||||||
/// write!(f, [text(&std::format!("Formatted {count}."), None)])
|
/// write!(f, [text(&std::format!("Formatted {count}."))])
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// format!(SimpleFormatContext::default(), [value]).expect("Formatting once works fine");
|
/// format!(SimpleFormatContext::default(), [value]).expect("Formatting once works fine");
|
||||||
|
|
|
||||||
|
|
@ -346,10 +346,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::SourcePosition(position) => {
|
FormatElement::SourcePosition(position) => {
|
||||||
write!(
|
write!(f, [text(&std::format!("source_position({position:?})"))])?;
|
||||||
f,
|
|
||||||
[text(&std::format!("source_position({position:?})"), None)]
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::LineSuffixBoundary => {
|
FormatElement::LineSuffixBoundary => {
|
||||||
|
|
@ -360,7 +357,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
write!(f, [token("best_fitting(")])?;
|
write!(f, [token("best_fitting(")])?;
|
||||||
|
|
||||||
if *mode != BestFittingMode::FirstLine {
|
if *mode != BestFittingMode::FirstLine {
|
||||||
write!(f, [text(&std::format!("mode: {mode:?}, "), None)])?;
|
write!(f, [text(&std::format!("mode: {mode:?}, "))])?;
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(f, [token("[")])?;
|
write!(f, [token("[")])?;
|
||||||
|
|
@ -392,17 +389,14 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
text(&std::format!("<interned {index}>"), None),
|
text(&std::format!("<interned {index}>")),
|
||||||
space(),
|
space(),
|
||||||
&&**interned,
|
&&**interned,
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Some(reference) => {
|
Some(reference) => {
|
||||||
write!(
|
write!(f, [text(&std::format!("<ref interned *{reference}>"))])?;
|
||||||
f,
|
|
||||||
[text(&std::format!("<ref interned *{reference}>"), None)]
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -421,7 +415,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("<END_TAG_WITHOUT_START<"),
|
token("<END_TAG_WITHOUT_START<"),
|
||||||
text(&std::format!("{:?}", tag.kind()), None),
|
text(&std::format!("{:?}", tag.kind())),
|
||||||
token(">>"),
|
token(">>"),
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -436,9 +430,9 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
token(")"),
|
token(")"),
|
||||||
soft_line_break_or_space(),
|
soft_line_break_or_space(),
|
||||||
token("ERROR<START_END_TAG_MISMATCH<start: "),
|
token("ERROR<START_END_TAG_MISMATCH<start: "),
|
||||||
text(&std::format!("{start_kind:?}"), None),
|
text(&std::format!("{start_kind:?}")),
|
||||||
token(", end: "),
|
token(", end: "),
|
||||||
text(&std::format!("{:?}", tag.kind()), None),
|
text(&std::format!("{:?}", tag.kind())),
|
||||||
token(">>")
|
token(">>")
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -470,7 +464,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("align("),
|
token("align("),
|
||||||
text(&count.to_string(), None),
|
text(&count.to_string()),
|
||||||
token(","),
|
token(","),
|
||||||
space(),
|
space(),
|
||||||
]
|
]
|
||||||
|
|
@ -482,7 +476,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("line_suffix("),
|
token("line_suffix("),
|
||||||
text(&std::format!("{reserved_width:?}"), None),
|
text(&std::format!("{reserved_width:?}")),
|
||||||
token(","),
|
token(","),
|
||||||
space(),
|
space(),
|
||||||
]
|
]
|
||||||
|
|
@ -499,11 +493,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
if let Some(group_id) = group.id() {
|
if let Some(group_id) = group.id() {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[text(&std::format!("\"{group_id:?}\"")), token(","), space(),]
|
||||||
text(&std::format!("\"{group_id:?}\""), None),
|
|
||||||
token(","),
|
|
||||||
space(),
|
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,11 +514,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
if let Some(group_id) = id {
|
if let Some(group_id) = id {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[text(&std::format!("\"{group_id:?}\"")), token(","), space(),]
|
||||||
text(&std::format!("\"{group_id:?}\""), None),
|
|
||||||
token(","),
|
|
||||||
space(),
|
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -561,7 +547,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("indent_if_group_breaks("),
|
token("indent_if_group_breaks("),
|
||||||
text(&std::format!("\"{id:?}\""), None),
|
text(&std::format!("\"{id:?}\"")),
|
||||||
token(","),
|
token(","),
|
||||||
space(),
|
space(),
|
||||||
]
|
]
|
||||||
|
|
@ -581,11 +567,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
if let Some(group_id) = condition.group_id {
|
if let Some(group_id) = condition.group_id {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[text(&std::format!("\"{group_id:?}\"")), token(","), space()]
|
||||||
text(&std::format!("\"{group_id:?}\""), None),
|
|
||||||
token(","),
|
|
||||||
space(),
|
|
||||||
]
|
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -595,7 +577,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("label("),
|
token("label("),
|
||||||
text(&std::format!("\"{label_id:?}\""), None),
|
text(&std::format!("\"{label_id:?}\"")),
|
||||||
token(","),
|
token(","),
|
||||||
space(),
|
space(),
|
||||||
]
|
]
|
||||||
|
|
@ -664,7 +646,7 @@ impl Format<IrFormatContext<'_>> for &[FormatElement] {
|
||||||
ContentArrayEnd,
|
ContentArrayEnd,
|
||||||
token(")"),
|
token(")"),
|
||||||
soft_line_break_or_space(),
|
soft_line_break_or_space(),
|
||||||
text(&std::format!("<START_WITHOUT_END<{top:?}>>"), None),
|
text(&std::format!("<START_WITHOUT_END<{top:?}>>")),
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
@ -807,7 +789,7 @@ impl Format<IrFormatContext<'_>> for Condition {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("if_group_fits_on_line("),
|
token("if_group_fits_on_line("),
|
||||||
text(&std::format!("\"{id:?}\""), None),
|
text(&std::format!("\"{id:?}\"")),
|
||||||
token(")")
|
token(")")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
@ -816,7 +798,7 @@ impl Format<IrFormatContext<'_>> for Condition {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
token("if_group_breaks("),
|
token("if_group_breaks("),
|
||||||
text(&std::format!("\"{id:?}\""), None),
|
text(&std::format!("\"{id:?}\"")),
|
||||||
token(")")
|
token(")")
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ pub trait MemoizeFormat<Context> {
|
||||||
/// let value = self.value.get();
|
/// let value = self.value.get();
|
||||||
/// self.value.set(value + 1);
|
/// self.value.set(value + 1);
|
||||||
///
|
///
|
||||||
/// write!(f, [text(&std::format!("Formatted {value} times."), None)])
|
/// write!(f, [text(&std::format!("Formatted {value} times."))])
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
|
|
@ -110,7 +110,7 @@ where
|
||||||
/// write!(f, [
|
/// write!(f, [
|
||||||
/// token("Count:"),
|
/// token("Count:"),
|
||||||
/// space(),
|
/// space(),
|
||||||
/// text(&std::format!("{current}"), None),
|
/// text(&std::format!("{current}")),
|
||||||
/// hard_line_break()
|
/// hard_line_break()
|
||||||
/// ])?;
|
/// ])?;
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ use std::marker::PhantomData;
|
||||||
use std::num::{NonZeroU16, NonZeroU8, TryFromIntError};
|
use std::num::{NonZeroU16, NonZeroU8, TryFromIntError};
|
||||||
|
|
||||||
use crate::format_element::document::Document;
|
use crate::format_element::document::Document;
|
||||||
use crate::printer::{Printer, PrinterOptions, SourceMapGeneration};
|
use crate::printer::{Printer, PrinterOptions};
|
||||||
pub use arguments::{Argument, Arguments};
|
pub use arguments::{Argument, Arguments};
|
||||||
pub use buffer::{
|
pub use buffer::{
|
||||||
Buffer, BufferExtensions, BufferSnapshot, Inspect, RemoveSoftLinesBuffer, VecBuffer,
|
Buffer, BufferExtensions, BufferSnapshot, Inspect, RemoveSoftLinesBuffer, VecBuffer,
|
||||||
|
|
@ -269,7 +269,6 @@ impl FormatOptions for SimpleFormatOptions {
|
||||||
line_width: self.line_width,
|
line_width: self.line_width,
|
||||||
indent_style: self.indent_style,
|
indent_style: self.indent_style,
|
||||||
indent_width: self.indent_width,
|
indent_width: self.indent_width,
|
||||||
source_map_generation: SourceMapGeneration::Enabled,
|
|
||||||
..PrinterOptions::default()
|
..PrinterOptions::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -433,28 +432,40 @@ impl Printed {
|
||||||
std::mem::take(&mut self.verbatim_ranges)
|
std::mem::take(&mut self.verbatim_ranges)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Slices the formatted code to the sub-slices that covers the passed `source_range`.
|
/// Slices the formatted code to the sub-slices that covers the passed `source_range` in `source`.
|
||||||
///
|
///
|
||||||
/// The implementation uses the source map generated during formatting to find the closest range
|
/// The implementation uses the source map generated during formatting to find the closest range
|
||||||
/// in the formatted document that covers `source_range` or more. The returned slice
|
/// in the formatted document that covers `source_range` or more. The returned slice
|
||||||
/// matches the `source_range` exactly (except indent, see below) if the formatter emits [`FormatElement::SourcePosition`] for
|
/// matches the `source_range` exactly (except indent, see below) if the formatter emits [`FormatElement::SourcePosition`] for
|
||||||
/// the range's offsets.
|
/// the range's offsets.
|
||||||
///
|
///
|
||||||
|
/// ## Indentation
|
||||||
|
/// The indentation before `source_range.start` is replaced with the indentation returned by the formatter
|
||||||
|
/// to fix up incorrectly intended code.
|
||||||
|
///
|
||||||
/// Returns the entire document if the source map is empty.
|
/// Returns the entire document if the source map is empty.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `source_range` points to offsets that are not in the bounds of `source`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn slice_range(self, source_range: TextRange) -> PrintedRange {
|
pub fn slice_range(self, source_range: TextRange, source: &str) -> PrintedRange {
|
||||||
let mut start_marker: Option<SourceMarker> = None;
|
let mut start_marker: Option<SourceMarker> = None;
|
||||||
let mut end_marker: Option<SourceMarker> = None;
|
let mut end_marker: Option<SourceMarker> = None;
|
||||||
|
|
||||||
// Note: The printer can generate multiple source map entries for the same source position.
|
// Note: The printer can generate multiple source map entries for the same source position.
|
||||||
// For example if you have:
|
// For example if you have:
|
||||||
|
// * token("a + b")
|
||||||
// * `source_position(276)`
|
// * `source_position(276)`
|
||||||
// * `token("def")`
|
// * `token(")")`
|
||||||
// * `token("foo")`
|
// * `source_position(276)`
|
||||||
// * `source_position(284)`
|
// * `hard_line_break`
|
||||||
// The printer uses the source position 276 for both the tokens `def` and `foo` because that's the only position it knows of.
|
// The printer uses the source position 276 for both the tokens `)` and the `\n` because
|
||||||
|
// there were multiple `source_position` entries in the IR with the same offset.
|
||||||
|
// This can happen if multiple nodes start or end at the same position. A common example
|
||||||
|
// for this are expressions and expression statement that always end at the same offset.
|
||||||
//
|
//
|
||||||
// Warning: Source markers are often emitted sorted by their source position but it's not guaranteed.
|
// Warning: Source markers are often emitted sorted by their source position but it's not guaranteed
|
||||||
|
// and depends on the emitted `IR`.
|
||||||
// They are only guaranteed to be sorted in increasing order by their destination position.
|
// They are only guaranteed to be sorted in increasing order by their destination position.
|
||||||
for marker in self.sourcemap {
|
for marker in self.sourcemap {
|
||||||
// Take the closest start marker, but skip over start_markers that have the same start.
|
// Take the closest start marker, but skip over start_markers that have the same start.
|
||||||
|
|
@ -471,17 +482,44 @@ impl Printed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = start_marker.map(|marker| marker.dest).unwrap_or_default();
|
let (source_start, formatted_start) = start_marker
|
||||||
let end = end_marker.map_or_else(|| self.code.text_len(), |marker| marker.dest);
|
.map(|marker| (marker.source, marker.dest))
|
||||||
let code_range = TextRange::new(start, end);
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let (source_end, formatted_end) = end_marker
|
||||||
|
.map_or((source.text_len(), self.code.text_len()), |marker| {
|
||||||
|
(marker.source, marker.dest)
|
||||||
|
});
|
||||||
|
|
||||||
|
let source_range = TextRange::new(source_start, source_end);
|
||||||
|
let formatted_range = TextRange::new(formatted_start, formatted_end);
|
||||||
|
|
||||||
|
// Extend both ranges to include the indentation
|
||||||
|
let source_range = extend_range_to_include_indent(source_range, source);
|
||||||
|
let formatted_range = extend_range_to_include_indent(formatted_range, &self.code);
|
||||||
|
|
||||||
PrintedRange {
|
PrintedRange {
|
||||||
code: self.code[code_range].to_string(),
|
code: self.code[formatted_range].to_string(),
|
||||||
source_range,
|
source_range,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extends `range` backwards (by reducing `range.start`) to include any directly preceding whitespace (`\t` or ` `).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `range.start` is out of `source`'s bounds.
|
||||||
|
fn extend_range_to_include_indent(range: TextRange, source: &str) -> TextRange {
|
||||||
|
let whitespace_len: TextSize = source[..usize::from(range.start())]
|
||||||
|
.chars()
|
||||||
|
.rev()
|
||||||
|
.take_while(|c| matches!(c, ' ' | '\t'))
|
||||||
|
.map(TextLen::text_len)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
TextRange::new(range.start() - whitespace_len, range.end())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
|
|
@ -537,7 +575,7 @@ pub type FormatResult<F> = Result<F, FormatError>;
|
||||||
/// impl Format<SimpleFormatContext> for Paragraph {
|
/// impl Format<SimpleFormatContext> for Paragraph {
|
||||||
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
|
/// fn fmt(&self, f: &mut Formatter<SimpleFormatContext>) -> FormatResult<()> {
|
||||||
/// write!(f, [
|
/// write!(f, [
|
||||||
/// text(&self.0, None),
|
/// text(&self.0),
|
||||||
/// hard_line_break(),
|
/// hard_line_break(),
|
||||||
/// ])
|
/// ])
|
||||||
/// }
|
/// }
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use drop_bomb::DebugDropBomb;
|
||||||
use unicode_width::UnicodeWidthChar;
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
pub use printer_options::*;
|
pub use printer_options::*;
|
||||||
use ruff_text_size::{Ranged, TextLen, TextSize};
|
use ruff_text_size::{TextLen, TextSize};
|
||||||
|
|
||||||
use crate::format_element::document::Document;
|
use crate::format_element::document::Document;
|
||||||
use crate::format_element::tag::{Condition, GroupMode};
|
use crate::format_element::tag::{Condition, GroupMode};
|
||||||
|
|
@ -76,6 +76,9 @@ impl<'a> Printer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Push any pending marker
|
||||||
|
self.push_marker();
|
||||||
|
|
||||||
Ok(Printed::new(
|
Ok(Printed::new(
|
||||||
self.state.buffer,
|
self.state.buffer,
|
||||||
None,
|
None,
|
||||||
|
|
@ -97,42 +100,38 @@ impl<'a> Printer<'a> {
|
||||||
let args = stack.top();
|
let args = stack.top();
|
||||||
|
|
||||||
match element {
|
match element {
|
||||||
FormatElement::Space => self.print_text(Text::Token(" "), None),
|
FormatElement::Space => self.print_text(Text::Token(" ")),
|
||||||
FormatElement::Token { text } => self.print_text(Text::Token(text), None),
|
FormatElement::Token { text } => self.print_text(Text::Token(text)),
|
||||||
FormatElement::Text { text, text_width } => self.print_text(
|
FormatElement::Text { text, text_width } => self.print_text(Text::Text {
|
||||||
Text::Text {
|
text,
|
||||||
text,
|
text_width: *text_width,
|
||||||
text_width: *text_width,
|
}),
|
||||||
},
|
|
||||||
None,
|
|
||||||
),
|
|
||||||
FormatElement::SourceCodeSlice { slice, text_width } => {
|
FormatElement::SourceCodeSlice { slice, text_width } => {
|
||||||
let text = slice.text(self.source_code);
|
let text = slice.text(self.source_code);
|
||||||
self.print_text(
|
self.print_text(Text::Text {
|
||||||
Text::Text {
|
text,
|
||||||
text,
|
text_width: *text_width,
|
||||||
text_width: *text_width,
|
});
|
||||||
},
|
|
||||||
Some(slice.range()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
FormatElement::Line(line_mode) => {
|
FormatElement::Line(line_mode) => {
|
||||||
if args.mode().is_flat()
|
if args.mode().is_flat()
|
||||||
&& matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace)
|
&& matches!(line_mode, LineMode::Soft | LineMode::SoftOrSpace)
|
||||||
{
|
{
|
||||||
if line_mode == &LineMode::SoftOrSpace {
|
if line_mode == &LineMode::SoftOrSpace {
|
||||||
self.print_text(Text::Token(" "), None);
|
self.print_text(Text::Token(" "));
|
||||||
}
|
}
|
||||||
} else if self.state.line_suffixes.has_pending() {
|
} else if self.state.line_suffixes.has_pending() {
|
||||||
self.flush_line_suffixes(queue, stack, Some(element));
|
self.flush_line_suffixes(queue, stack, Some(element));
|
||||||
} else {
|
} else {
|
||||||
// Only print a newline if the current line isn't already empty
|
// Only print a newline if the current line isn't already empty
|
||||||
if self.state.line_width > 0 {
|
if self.state.line_width > 0 {
|
||||||
|
self.push_marker();
|
||||||
self.print_char('\n');
|
self.print_char('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print a second line break if this is an empty line
|
// Print a second line break if this is an empty line
|
||||||
if line_mode == &LineMode::Empty {
|
if line_mode == &LineMode::Empty {
|
||||||
|
self.push_marker();
|
||||||
self.print_char('\n');
|
self.print_char('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,14 +144,11 @@ impl<'a> Printer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::SourcePosition(position) => {
|
FormatElement::SourcePosition(position) => {
|
||||||
self.state.source_position = *position;
|
|
||||||
// The printer defers printing indents until the next text
|
// The printer defers printing indents until the next text
|
||||||
// is printed. Pushing the marker now would mean that the
|
// is printed. Pushing the marker now would mean that the
|
||||||
// mapped range includes the indent range, which we don't want.
|
// mapped range includes the indent range, which we don't want.
|
||||||
// Only add a marker if we're not in an indented context, e.g. at the end of the file.
|
// Queue the source map position and emit it when printing the next character
|
||||||
if self.state.pending_indent.is_empty() {
|
self.state.pending_source_position = Some(*position);
|
||||||
self.push_marker();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatElement::LineSuffixBoundary => {
|
FormatElement::LineSuffixBoundary => {
|
||||||
|
|
@ -444,7 +440,7 @@ impl<'a> Printer<'a> {
|
||||||
Ok(print_mode)
|
Ok(print_mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_text(&mut self, text: Text, source_range: Option<TextRange>) {
|
fn print_text(&mut self, text: Text) {
|
||||||
if !self.state.pending_indent.is_empty() {
|
if !self.state.pending_indent.is_empty() {
|
||||||
let (indent_char, repeat_count) = match self.options.indent_style() {
|
let (indent_char, repeat_count) = match self.options.indent_style() {
|
||||||
IndentStyle::Tab => ('\t', 1),
|
IndentStyle::Tab => ('\t', 1),
|
||||||
|
|
@ -467,19 +463,6 @@ impl<'a> Printer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert source map markers before and after the token
|
|
||||||
//
|
|
||||||
// If the token has source position information the start marker
|
|
||||||
// will use the start position of the original token, and the end
|
|
||||||
// marker will use that position + the text length of the token
|
|
||||||
//
|
|
||||||
// If the token has no source position (was created by the formatter)
|
|
||||||
// both the start and end marker will use the last known position
|
|
||||||
// in the input source (from state.source_position)
|
|
||||||
if let Some(range) = source_range {
|
|
||||||
self.state.source_position = range.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push_marker();
|
self.push_marker();
|
||||||
|
|
||||||
match text {
|
match text {
|
||||||
|
|
@ -502,21 +485,15 @@ impl<'a> Printer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(range) = source_range {
|
|
||||||
self.state.source_position = range.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push_marker();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_marker(&mut self) {
|
fn push_marker(&mut self) {
|
||||||
if self.options.source_map_generation.is_disabled() {
|
let Some(source_position) = self.state.pending_source_position.take() else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
let marker = SourceMarker {
|
let marker = SourceMarker {
|
||||||
source: self.state.source_position,
|
source: source_position,
|
||||||
dest: self.state.buffer.text_len(),
|
dest: self.state.buffer.text_len(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -897,7 +874,7 @@ enum FillPairLayout {
|
||||||
struct PrinterState<'a> {
|
struct PrinterState<'a> {
|
||||||
buffer: String,
|
buffer: String,
|
||||||
source_markers: Vec<SourceMarker>,
|
source_markers: Vec<SourceMarker>,
|
||||||
source_position: TextSize,
|
pending_source_position: Option<TextSize>,
|
||||||
pending_indent: Indention,
|
pending_indent: Indention,
|
||||||
measured_group_fits: bool,
|
measured_group_fits: bool,
|
||||||
line_width: u32,
|
line_width: u32,
|
||||||
|
|
@ -1752,7 +1729,7 @@ a",
|
||||||
let result = format_with_options(
|
let result = format_with_options(
|
||||||
&format_args![
|
&format_args![
|
||||||
token("function main() {"),
|
token("function main() {"),
|
||||||
block_indent(&text("let x = `This is a multiline\nstring`;", None)),
|
block_indent(&text("let x = `This is a multiline\nstring`;")),
|
||||||
token("}"),
|
token("}"),
|
||||||
hard_line_break()
|
hard_line_break()
|
||||||
],
|
],
|
||||||
|
|
@ -1769,7 +1746,7 @@ a",
|
||||||
fn it_breaks_a_group_if_a_string_contains_a_newline() {
|
fn it_breaks_a_group_if_a_string_contains_a_newline() {
|
||||||
let result = format(&FormatArrayElements {
|
let result = format(&FormatArrayElements {
|
||||||
items: vec![
|
items: vec![
|
||||||
&text("`This is a string spanning\ntwo lines`", None),
|
&text("`This is a string spanning\ntwo lines`"),
|
||||||
&token("\"b\""),
|
&token("\"b\""),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,6 @@ pub struct PrinterOptions {
|
||||||
|
|
||||||
/// The type of line ending to apply to the printed input
|
/// The type of line ending to apply to the printed input
|
||||||
pub line_ending: LineEnding,
|
pub line_ending: LineEnding,
|
||||||
|
|
||||||
/// Whether the printer should build a source map that allows mapping positions in the source document
|
|
||||||
/// to positions in the formatted document.
|
|
||||||
pub source_map_generation: SourceMapGeneration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, O> From<&'a O> for PrinterOptions
|
impl<'a, O> From<&'a O> for PrinterOptions
|
||||||
|
|
|
||||||
1
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/end_of_file.py
vendored
Normal file
1
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/end_of_file.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
a + b<RANGE_START><RANGE_END>
|
||||||
13
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/parentheses.py
vendored
Normal file
13
crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/parentheses.py
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
def needs_parentheses( ) -> bool:
|
||||||
|
return item.sizing_mode is None and <RANGE_START>item.width_policy == "auto" and item.height_policy == "automatic"<RANGE_END>
|
||||||
|
|
||||||
|
def no_longer_needs_parentheses( ) -> bool:
|
||||||
|
return (
|
||||||
|
<RANGE_START>item.width_policy == "auto"
|
||||||
|
and item.height_policy == "automatic"<RANGE_END>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def format_range_after_inserted_parens ():
|
||||||
|
a and item.sizing_mode is None and item.width_policy == "auto" and item.height_policy == "automatic"<RANGE_START>
|
||||||
|
print("Format this" ) <RANGE_END>
|
||||||
|
|
@ -67,3 +67,19 @@ def convert_str(value: str) -> str: # Trailing comment
|
||||||
<RANGE_END>
|
<RANGE_END>
|
||||||
def test ():
|
def test ():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_indent():
|
||||||
|
<RANGE_START># A misaligned comment<RANGE_END>
|
||||||
|
print("test")
|
||||||
|
|
||||||
|
|
||||||
|
# This demonstrates the use case where a user inserts a new function or class after an existing function.
|
||||||
|
# In this case, we should avoid formatting the node that directly precedes the new function/class.
|
||||||
|
# However, the problem is that the preceding node **must** be formatted to determine the whitespace between the two statements.
|
||||||
|
def test_start ():
|
||||||
|
print("Ideally this gets not reformatted" )
|
||||||
|
|
||||||
|
<RANGE_START>
|
||||||
|
def new_function_inserted_after_test_start ():
|
||||||
|
print("This should get formatted" )<RANGE_END>
|
||||||
|
|
|
||||||
|
|
@ -430,31 +430,25 @@ pub(crate) struct FormatNormalizedComment<'a> {
|
||||||
|
|
||||||
impl Format<PyFormatContext<'_>> for FormatNormalizedComment<'_> {
|
impl Format<PyFormatContext<'_>> for FormatNormalizedComment<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
|
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
|
||||||
|
let write_sourcemap = f.options().source_map_generation().is_enabled();
|
||||||
|
|
||||||
|
write_sourcemap
|
||||||
|
.then_some(source_position(self.range.start()))
|
||||||
|
.fmt(f)?;
|
||||||
|
|
||||||
match self.comment {
|
match self.comment {
|
||||||
Cow::Borrowed(borrowed) => {
|
Cow::Borrowed(borrowed) => {
|
||||||
source_text_slice(TextRange::at(self.range.start(), borrowed.text_len())).fmt(f)?;
|
source_text_slice(TextRange::at(self.range.start(), borrowed.text_len())).fmt(f)?;
|
||||||
|
|
||||||
// Write the end position if the borrowed comment is shorter than the original comment
|
|
||||||
// So that we still can map back the end of a comment to the formatted code.
|
|
||||||
if f.options().source_map_generation().is_enabled()
|
|
||||||
&& self.range.len() != borrowed.text_len()
|
|
||||||
{
|
|
||||||
source_position(self.range.end()).fmt(f)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Cow::Owned(ref owned) => {
|
Cow::Owned(ref owned) => {
|
||||||
write!(
|
text(owned).fmt(f)?;
|
||||||
f,
|
|
||||||
[
|
|
||||||
text(owned, Some(self.range.start())),
|
|
||||||
source_position(self.range.end())
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write_sourcemap
|
||||||
|
.then_some(source_position(self.range.end()))
|
||||||
|
.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ impl FormatNodeRule<ExprNumberLiteral> for FormatExprNumberLiteral {
|
||||||
|
|
||||||
match normalized {
|
match normalized {
|
||||||
Cow::Borrowed(_) => source_text_slice(range).fmt(f),
|
Cow::Borrowed(_) => source_text_slice(range).fmt(f),
|
||||||
Cow::Owned(normalized) => text(&normalized, Some(range.start())).fmt(f),
|
Cow::Owned(normalized) => text(&normalized).fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Number::Float(_) => {
|
Number::Float(_) => {
|
||||||
|
|
@ -30,7 +30,7 @@ impl FormatNodeRule<ExprNumberLiteral> for FormatExprNumberLiteral {
|
||||||
|
|
||||||
match normalized {
|
match normalized {
|
||||||
Cow::Borrowed(_) => source_text_slice(range).fmt(f),
|
Cow::Borrowed(_) => source_text_slice(range).fmt(f),
|
||||||
Cow::Owned(normalized) => text(&normalized, Some(range.start())).fmt(f),
|
Cow::Owned(normalized) => text(&normalized).fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Number::Complex { .. } => {
|
Number::Complex { .. } => {
|
||||||
|
|
@ -43,7 +43,7 @@ impl FormatNodeRule<ExprNumberLiteral> for FormatExprNumberLiteral {
|
||||||
source_text_slice(range.sub_end(TextSize::from(1))).fmt(f)?;
|
source_text_slice(range.sub_end(TextSize::from(1))).fmt(f)?;
|
||||||
}
|
}
|
||||||
Cow::Owned(normalized) => {
|
Cow::Owned(normalized) => {
|
||||||
text(&normalized, Some(range.start())).fmt(f)?;
|
text(&normalized).fmt(f)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -304,30 +304,25 @@ fn format_with_parentheses_comments(
|
||||||
// Custom FormatNodeRule::fmt variant that only formats the inner comments
|
// Custom FormatNodeRule::fmt variant that only formats the inner comments
|
||||||
let format_node_rule_fmt = format_with(|f| {
|
let format_node_rule_fmt = format_with(|f| {
|
||||||
// No need to handle suppression comments, those are statement only
|
// No need to handle suppression comments, those are statement only
|
||||||
leading_comments(leading_inner).fmt(f)?;
|
write!(
|
||||||
|
f,
|
||||||
let is_source_map_enabled = f.options().source_map_generation().is_enabled();
|
[
|
||||||
|
leading_comments(leading_inner),
|
||||||
if is_source_map_enabled {
|
fmt_fields,
|
||||||
source_position(expression.start()).fmt(f)?;
|
trailing_comments(trailing_inner)
|
||||||
}
|
]
|
||||||
|
)
|
||||||
fmt_fields.fmt(f)?;
|
|
||||||
|
|
||||||
if is_source_map_enabled {
|
|
||||||
source_position(expression.end()).fmt(f)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
trailing_comments(trailing_inner).fmt(f)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// The actual parenthesized formatting
|
// The actual parenthesized formatting
|
||||||
parenthesized("(", &format_node_rule_fmt, ")")
|
write!(
|
||||||
.with_dangling_comments(parentheses_comment)
|
f,
|
||||||
.fmt(f)?;
|
[
|
||||||
trailing_comments(trailing_outer).fmt(f)?;
|
parenthesized("(", &format_node_rule_fmt, ")")
|
||||||
|
.with_dangling_comments(parentheses_comment),
|
||||||
Ok(())
|
trailing_comments(trailing_outer)
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wraps an expression in an optional parentheses except if its [`NeedsParentheses::needs_parentheses`] implementation
|
/// Wraps an expression in an optional parentheses except if its [`NeedsParentheses::needs_parentheses`] implementation
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use tracing::Level;
|
||||||
|
|
||||||
pub use range::format_range;
|
pub use range::format_range;
|
||||||
use ruff_formatter::prelude::*;
|
use ruff_formatter::prelude::*;
|
||||||
use ruff_formatter::{format, FormatError, Formatted, PrintError, Printed, SourceCode};
|
use ruff_formatter::{format, write, FormatError, Formatted, PrintError, Printed, SourceCode};
|
||||||
use ruff_python_ast::AstNode;
|
use ruff_python_ast::AstNode;
|
||||||
use ruff_python_ast::Mod;
|
use ruff_python_ast::Mod;
|
||||||
use ruff_python_index::tokens_and_ranges;
|
use ruff_python_index::tokens_and_ranges;
|
||||||
|
|
@ -19,6 +19,7 @@ pub use crate::options::{
|
||||||
DocstringCode, DocstringCodeLineWidth, MagicTrailingComma, PreviewMode, PyFormatOptions,
|
DocstringCode, DocstringCodeLineWidth, MagicTrailingComma, PreviewMode, PyFormatOptions,
|
||||||
PythonVersion, QuoteStyle,
|
PythonVersion, QuoteStyle,
|
||||||
};
|
};
|
||||||
|
use crate::range::is_logical_line;
|
||||||
pub use crate::shared_traits::{AsFormat, FormattedIter, FormattedIterExt, IntoFormat};
|
pub use crate::shared_traits::{AsFormat, FormattedIter, FormattedIterExt, IntoFormat};
|
||||||
use crate::verbatim::suppressed_node;
|
use crate::verbatim::suppressed_node;
|
||||||
|
|
||||||
|
|
@ -59,19 +60,27 @@ where
|
||||||
} else {
|
} else {
|
||||||
leading_comments(node_comments.leading).fmt(f)?;
|
leading_comments(node_comments.leading).fmt(f)?;
|
||||||
|
|
||||||
let is_source_map_enabled = f.options().source_map_generation().is_enabled();
|
let node_ref = node.as_any_node_ref();
|
||||||
|
|
||||||
if is_source_map_enabled {
|
// Emit source map information for nodes that are valid "narrowing" targets
|
||||||
source_position(node.start()).fmt(f)?;
|
// in range formatting. Never emit source map information if they're disabled
|
||||||
}
|
// for performance reasons.
|
||||||
|
let emit_source_position = (is_logical_line(node_ref) || node_ref.is_mod_module())
|
||||||
|
&& f.options().source_map_generation().is_enabled();
|
||||||
|
|
||||||
|
emit_source_position
|
||||||
|
.then_some(source_position(node.start()))
|
||||||
|
.fmt(f)?;
|
||||||
|
|
||||||
self.fmt_fields(node, f)?;
|
self.fmt_fields(node, f)?;
|
||||||
|
|
||||||
if is_source_map_enabled {
|
write!(
|
||||||
source_position(node.end()).fmt(f)?;
|
f,
|
||||||
}
|
[
|
||||||
|
emit_source_position.then_some(source_position(node.end())),
|
||||||
trailing_comments(node_comments.trailing).fmt(f)
|
trailing_comments(node_comments.trailing)
|
||||||
|
]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,15 +258,36 @@ def main() -> None:
|
||||||
#[ignore]
|
#[ignore]
|
||||||
#[test]
|
#[test]
|
||||||
fn range_formatting_quick_test() {
|
fn range_formatting_quick_test() {
|
||||||
let source = r#"def test2( a): print("body" )
|
let source = r#"def convert_str(value: str) -> str: # Trailing comment
|
||||||
"#;
|
"""Return a string as-is."""
|
||||||
|
|
||||||
let start = TextSize::new(20);
|
<RANGE_START>
|
||||||
let end = TextSize::new(35);
|
|
||||||
|
return value # Trailing comment
|
||||||
|
<RANGE_END>"#;
|
||||||
|
|
||||||
|
let mut source = source.to_string();
|
||||||
|
|
||||||
|
let start = TextSize::try_from(
|
||||||
|
source
|
||||||
|
.find("<RANGE_START>")
|
||||||
|
.expect("Start marker not found"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
source.replace_range(
|
||||||
|
start.to_usize()..start.to_usize() + "<RANGE_START>".len(),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
let end =
|
||||||
|
TextSize::try_from(source.find("<RANGE_END>").expect("End marker not found")).unwrap();
|
||||||
|
|
||||||
|
source.replace_range(end.to_usize()..end.to_usize() + "<RANGE_END>".len(), "");
|
||||||
|
|
||||||
let source_type = PySourceType::Python;
|
let source_type = PySourceType::Python;
|
||||||
let options = PyFormatOptions::from_source_type(source_type);
|
let options = PyFormatOptions::from_source_type(source_type);
|
||||||
let printed = format_range(source, TextRange::new(start, end), options).unwrap();
|
let printed = format_range(&source, TextRange::new(start, end), options).unwrap();
|
||||||
|
|
||||||
let mut formatted = source.to_string();
|
let mut formatted = source.to_string();
|
||||||
formatted.replace_range(
|
formatted.replace_range(
|
||||||
|
|
@ -267,9 +297,11 @@ def main() -> None:
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatted,
|
formatted,
|
||||||
r#"def test2(a):
|
r#"print ( "format me" )
|
||||||
print("body")
|
print("format me")
|
||||||
"#
|
print("format me")
|
||||||
|
print ( "format me" )
|
||||||
|
print ( "format me" )"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,7 +332,7 @@ def main() -> None:
|
||||||
while let Some(word) = words.next() {
|
while let Some(word) = words.next() {
|
||||||
let is_last = words.peek().is_none();
|
let is_last = words.peek().is_none();
|
||||||
let format_word = format_with(|f| {
|
let format_word = format_with(|f| {
|
||||||
write!(f, [text(word, None)])?;
|
write!(f, [text(word)])?;
|
||||||
|
|
||||||
if is_last {
|
if is_last {
|
||||||
write!(f, [token("\"")])?;
|
write!(f, [token("\"")])?;
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,6 @@ impl FormatOptions for PyFormatOptions {
|
||||||
line_width: self.line_width,
|
line_width: self.line_width,
|
||||||
line_ending: self.line_ending,
|
line_ending: self.line_ending,
|
||||||
indent_style: self.indent_style,
|
indent_style: self.indent_style,
|
||||||
source_map_generation: self.source_map_generation,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ impl Format<PyFormatContext<'_>> for DotDelimitedIdentifier<'_> {
|
||||||
.chars()
|
.chars()
|
||||||
.filter(|c| !is_python_whitespace(*c) && !matches!(c, '\n' | '\r' | '\\'))
|
.filter(|c| !is_python_whitespace(*c) && !matches!(c, '\n' | '\r' | '\\'))
|
||||||
.collect();
|
.collect();
|
||||||
text(&no_whitespace, Some(self.0.start())).fmt(f)
|
text(&no_whitespace).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
source_text_slice(self.0.range()).fmt(f)
|
source_text_slice(self.0.range()).fmt(f)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ pub fn format_range(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let printed = formatted.print_with_indent(base_indent)?;
|
let printed = formatted.print_with_indent(base_indent)?;
|
||||||
Ok(printed.slice_range(narrowed_range))
|
Ok(printed.slice_range(narrowed_range, source))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds the node with the minimum covering range of `range`.
|
/// Finds the node with the minimum covering range of `range`.
|
||||||
|
|
@ -420,8 +420,13 @@ impl PreorderVisitor<'_> for NarrowRange<'_> {
|
||||||
// Find the end offset of the closest node to the end offset of the formatting range.
|
// Find the end offset of the closest node to the end offset of the formatting range.
|
||||||
// We do this by iterating over end positions that we know generate source map entries end pick the end
|
// We do this by iterating over end positions that we know generate source map entries end pick the end
|
||||||
// that ends closest or after the searched range's end.
|
// that ends closest or after the searched range's end.
|
||||||
let trailing_comments = self.context.comments().trailing(node);
|
self.narrow(
|
||||||
self.narrow(trailing_comments);
|
self.context
|
||||||
|
.comments()
|
||||||
|
.trailing(node)
|
||||||
|
.iter()
|
||||||
|
.filter(|comment| comment.line_position().is_own_line()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_body(&mut self, body: &'_ [Stmt]) {
|
fn visit_body(&mut self, body: &'_ [Stmt]) {
|
||||||
|
|
@ -552,7 +557,7 @@ impl NarrowRange<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn is_logical_line(node: AnyNodeRef) -> bool {
|
pub(crate) const fn is_logical_line(node: AnyNodeRef) -> bool {
|
||||||
// Make sure to update [`FormatEnclosingLine`] when changing this.
|
// Make sure to update [`FormatEnclosingLine`] when changing this.
|
||||||
node.is_statement()
|
node.is_statement()
|
||||||
|| node.is_decorator()
|
|| node.is_decorator()
|
||||||
|
|
|
||||||
|
|
@ -772,9 +772,17 @@ impl Format<PyFormatContext<'_>> for DocstringStmt<'_> {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
leading_comments(node_comments.leading),
|
leading_comments(node_comments.leading),
|
||||||
|
f.options()
|
||||||
|
.source_map_generation()
|
||||||
|
.is_enabled()
|
||||||
|
.then_some(source_position(self.docstring.start())),
|
||||||
string_literal
|
string_literal
|
||||||
.format()
|
.format()
|
||||||
.with_options(ExprStringLiteralKind::Docstring),
|
.with_options(ExprStringLiteralKind::Docstring),
|
||||||
|
f.options()
|
||||||
|
.source_map_generation()
|
||||||
|
.is_enabled()
|
||||||
|
.then_some(source_position(self.docstring.end())),
|
||||||
]
|
]
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
use std::{borrow::Cow, collections::VecDeque};
|
use std::{borrow::Cow, collections::VecDeque};
|
||||||
|
|
||||||
|
use ruff_formatter::printer::SourceMapGeneration;
|
||||||
use ruff_python_parser::ParseError;
|
use ruff_python_parser::ParseError;
|
||||||
use {once_cell::sync::Lazy, regex::Regex};
|
use {once_cell::sync::Lazy, regex::Regex};
|
||||||
use {
|
use {
|
||||||
|
|
@ -116,14 +117,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
|
||||||
let mut lines = docstring.lines().peekable();
|
let mut lines = docstring.lines().peekable();
|
||||||
|
|
||||||
// Start the string
|
// Start the string
|
||||||
write!(
|
write!(f, [normalized.prefix, normalized.quotes])?;
|
||||||
f,
|
|
||||||
[
|
|
||||||
normalized.prefix,
|
|
||||||
normalized.quotes,
|
|
||||||
source_position(normalized.start()),
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
// We track where in the source docstring we are (in source code byte offsets)
|
// We track where in the source docstring we are (in source code byte offsets)
|
||||||
let mut offset = normalized.start();
|
let mut offset = normalized.start();
|
||||||
|
|
||||||
|
|
@ -152,7 +146,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
|
||||||
if already_normalized {
|
if already_normalized {
|
||||||
source_text_slice(trimmed_line_range).fmt(f)?;
|
source_text_slice(trimmed_line_range).fmt(f)?;
|
||||||
} else {
|
} else {
|
||||||
text(trim_both, Some(trimmed_line_range.start())).fmt(f)?;
|
text(trim_both).fmt(f)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset += first.text_len();
|
offset += first.text_len();
|
||||||
|
|
@ -205,7 +199,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
|
||||||
space().fmt(f)?;
|
space().fmt(f)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(f, [source_position(normalized.end()), normalized.quotes])
|
write!(f, [normalized.quotes])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contains_unescaped_newline(haystack: &str) -> bool {
|
fn contains_unescaped_newline(haystack: &str) -> bool {
|
||||||
|
|
@ -404,7 +398,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||||
// prepend the in-docstring indentation to the string.
|
// prepend the in-docstring indentation to the string.
|
||||||
let indent_len = indentation_length(trim_end) - self.stripped_indentation_length;
|
let indent_len = indentation_length(trim_end) - self.stripped_indentation_length;
|
||||||
let in_docstring_indent = " ".repeat(usize::from(indent_len)) + trim_end.trim_start();
|
let in_docstring_indent = " ".repeat(usize::from(indent_len)) + trim_end.trim_start();
|
||||||
text(&in_docstring_indent, Some(line.offset)).fmt(self.f)?;
|
text(&in_docstring_indent).fmt(self.f)?;
|
||||||
} else {
|
} else {
|
||||||
// Take the string with the trailing whitespace removed, then also
|
// Take the string with the trailing whitespace removed, then also
|
||||||
// skip the leading whitespace.
|
// skip the leading whitespace.
|
||||||
|
|
@ -414,11 +408,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||||
source_text_slice(trimmed_line_range).fmt(self.f)?;
|
source_text_slice(trimmed_line_range).fmt(self.f)?;
|
||||||
} else {
|
} else {
|
||||||
// All indents are ascii spaces, so the slicing is correct.
|
// All indents are ascii spaces, so the slicing is correct.
|
||||||
text(
|
text(&trim_end[usize::from(self.stripped_indentation_length)..]).fmt(self.f)?;
|
||||||
&trim_end[usize::from(self.stripped_indentation_length)..],
|
|
||||||
Some(trimmed_line_range.start()),
|
|
||||||
)
|
|
||||||
.fmt(self.f)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -495,7 +485,8 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
||||||
// tabs will get erased anyway, we just clobber them here
|
// tabs will get erased anyway, we just clobber them here
|
||||||
// instead of later, and as a result, get more consistent
|
// instead of later, and as a result, get more consistent
|
||||||
// results.
|
// results.
|
||||||
.with_indent_style(IndentStyle::Space);
|
.with_indent_style(IndentStyle::Space)
|
||||||
|
.with_source_map_generation(SourceMapGeneration::Disabled);
|
||||||
let printed = match docstring_format_source(options, self.quote_char, &codeblob) {
|
let printed = match docstring_format_source(options, self.quote_char, &codeblob) {
|
||||||
Ok(printed) => printed,
|
Ok(printed) => printed,
|
||||||
Err(FormatModuleError::FormatError(err)) => return Err(err),
|
Err(FormatModuleError::FormatError(err)) => return Err(err),
|
||||||
|
|
|
||||||
|
|
@ -417,7 +417,7 @@ impl Format<PyFormatContext<'_>> for NormalizedString<'_> {
|
||||||
source_text_slice(self.range()).fmt(f)?;
|
source_text_slice(self.range()).fmt(f)?;
|
||||||
}
|
}
|
||||||
Cow::Owned(normalized) => {
|
Cow::Owned(normalized) => {
|
||||||
text(normalized, Some(self.start())).fmt(f)?;
|
text(normalized).fmt(f)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.quotes.fmt(f)
|
self.quotes.fmt(f)
|
||||||
|
|
|
||||||
|
|
@ -752,7 +752,14 @@ impl Format<PyFormatContext<'_>> for FormatVerbatimStatementRange {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Non empty line, write the text of the line
|
// Non empty line, write the text of the line
|
||||||
verbatim_text(trimmed_line_range).fmt(f)?;
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
source_position(trimmed_line_range.start()),
|
||||||
|
verbatim_text(trimmed_line_range),
|
||||||
|
source_position(trimmed_line_range.end())
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
|
||||||
// Write the line separator that terminates the line, except if it is the last line (that isn't separated by a hard line break).
|
// Write the line separator that terminates the line, except if it is the last line (that isn't separated by a hard line break).
|
||||||
if logical_line.has_trailing_newline {
|
if logical_line.has_trailing_newline {
|
||||||
|
|
@ -892,13 +899,7 @@ impl Format<PyFormatContext<'_>> for VerbatimText {
|
||||||
write!(f, [source_text_slice(self.verbatim_range)])?;
|
write!(f, [source_text_slice(self.verbatim_range)])?;
|
||||||
}
|
}
|
||||||
Cow::Owned(cleaned) => {
|
Cow::Owned(cleaned) => {
|
||||||
write!(
|
text(&cleaned).fmt(f)?;
|
||||||
f,
|
|
||||||
[
|
|
||||||
text(&cleaned, Some(self.verbatim_range.start())),
|
|
||||||
source_position(self.verbatim_range.end())
|
|
||||||
]
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -957,7 +958,9 @@ impl Format<PyFormatContext<'_>> for FormatSuppressedNode<'_> {
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
leading_comments(node_comments.leading),
|
leading_comments(node_comments.leading),
|
||||||
|
source_position(verbatim_range.start()),
|
||||||
verbatim_text(verbatim_range),
|
verbatim_text(verbatim_range),
|
||||||
|
source_position(verbatim_range.end()),
|
||||||
trailing_comments(node_comments.trailing)
|
trailing_comments(node_comments.trailing)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
@ -969,8 +972,17 @@ pub(crate) fn write_suppressed_clause_header(
|
||||||
header: ClauseHeader,
|
header: ClauseHeader,
|
||||||
f: &mut PyFormatter,
|
f: &mut PyFormatter,
|
||||||
) -> FormatResult<()> {
|
) -> FormatResult<()> {
|
||||||
|
let range = header.range(f.context().source())?;
|
||||||
|
|
||||||
// Write the outer comments and format the node as verbatim
|
// Write the outer comments and format the node as verbatim
|
||||||
write!(f, [verbatim_text(header.range(f.context().source())?)])?;
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
source_position(range.start()),
|
||||||
|
verbatim_text(range),
|
||||||
|
source_position(range.end())
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
|
||||||
let comments = f.context().comments();
|
let comments = f.context().comments();
|
||||||
header.visit(&mut |child| {
|
header.visit(&mut |child| {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/end_of_file.py
|
||||||
|
---
|
||||||
|
## Input
|
||||||
|
```python
|
||||||
|
a + b<RANGE_START><RANGE_END>```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
```python
|
||||||
|
a + b```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/parentheses.py
|
||||||
|
---
|
||||||
|
## Input
|
||||||
|
```python
|
||||||
|
def needs_parentheses( ) -> bool:
|
||||||
|
return item.sizing_mode is None and <RANGE_START>item.width_policy == "auto" and item.height_policy == "automatic"<RANGE_END>
|
||||||
|
|
||||||
|
def no_longer_needs_parentheses( ) -> bool:
|
||||||
|
return (
|
||||||
|
<RANGE_START>item.width_policy == "auto"
|
||||||
|
and item.height_policy == "automatic"<RANGE_END>
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def format_range_after_inserted_parens ():
|
||||||
|
a and item.sizing_mode is None and item.width_policy == "auto" and item.height_policy == "automatic"<RANGE_START>
|
||||||
|
print("Format this" ) <RANGE_END>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
```python
|
||||||
|
def needs_parentheses( ) -> bool:
|
||||||
|
return (
|
||||||
|
item.sizing_mode is None
|
||||||
|
and item.width_policy == "auto"
|
||||||
|
and item.height_policy == "automatic"
|
||||||
|
)
|
||||||
|
|
||||||
|
def no_longer_needs_parentheses( ) -> bool:
|
||||||
|
return item.width_policy == "auto" and item.height_policy == "automatic"
|
||||||
|
|
||||||
|
|
||||||
|
def format_range_after_inserted_parens ():
|
||||||
|
a and item.sizing_mode is None and item.width_policy == "auto" and item.height_policy == "automatic"
|
||||||
|
print("Format this")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -73,6 +73,22 @@ def convert_str(value: str) -> str: # Trailing comment
|
||||||
<RANGE_END>
|
<RANGE_END>
|
||||||
def test ():
|
def test ():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_indent():
|
||||||
|
<RANGE_START># A misaligned comment<RANGE_END>
|
||||||
|
print("test")
|
||||||
|
|
||||||
|
|
||||||
|
# This demonstrates the use case where a user inserts a new function or class after an existing function.
|
||||||
|
# In this case, we should avoid formatting the node that directly precedes the new function/class.
|
||||||
|
# However, the problem is that the preceding node **must** be formatted to determine the whitespace between the two statements.
|
||||||
|
def test_start ():
|
||||||
|
print("Ideally this gets not reformatted" )
|
||||||
|
|
||||||
|
<RANGE_START>
|
||||||
|
def new_function_inserted_after_test_start ():
|
||||||
|
print("This should get formatted" )<RANGE_END>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
|
@ -133,6 +149,22 @@ def convert_str(value: str) -> str: # Trailing comment
|
||||||
|
|
||||||
def test ():
|
def test ():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_comment_indent():
|
||||||
|
# A misaligned comment
|
||||||
|
print("test")
|
||||||
|
|
||||||
|
|
||||||
|
# This demonstrates the use case where a user inserts a new function or class after an existing function.
|
||||||
|
# In this case, we should avoid formatting the node that directly precedes the new function/class.
|
||||||
|
# However, the problem is that the preceding node **must** be formatted to determine the whitespace between the two statements.
|
||||||
|
def test_start ():
|
||||||
|
print("Ideally this gets not reformatted" )
|
||||||
|
|
||||||
|
|
||||||
|
def new_function_inserted_after_test_start():
|
||||||
|
print("This should get formatted")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ impl TextSize {
|
||||||
/// # use ruff_text_size::*;
|
/// # use ruff_text_size::*;
|
||||||
/// assert_eq!(TextSize::from(4).to_u32(), 4);
|
/// assert_eq!(TextSize::from(4).to_u32(), 4);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to_u32(&self) -> u32 {
|
pub const fn to_u32(&self) -> u32 {
|
||||||
self.raw
|
self.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ impl TextSize {
|
||||||
/// # use ruff_text_size::*;
|
/// # use ruff_text_size::*;
|
||||||
/// assert_eq!(TextSize::from(4).to_usize(), 4);
|
/// assert_eq!(TextSize::from(4).to_usize(), 4);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn to_usize(&self) -> usize {
|
pub const fn to_usize(&self) -> usize {
|
||||||
self.raw as usize
|
self.raw as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue