mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-31 03:54:57 +00:00 
			
		
		
		
	 ad265fa6bc
			
		
	
	
		ad265fa6bc
		
			
		
	
	
	
	
		
			
			## Summary This PR resolves an issue raised in https://github.com/astral-sh/ruff/discussions/7810, whereby we don't fix an f-string that exceeds the line length _even if_ the resultant code is _shorter_ than the current code. As part of this change, I've also refactored and extracted some common logic we use around "ensuring a fix isn't breaking the line length rules". ## Test Plan `cargo test`
		
			
				
	
	
		
			253 lines
		
	
	
	
		
			6.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
	
		
			6.8 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::error::Error;
 | |
| use std::hash::Hasher;
 | |
| use std::num::{NonZeroU16, NonZeroU8, ParseIntError};
 | |
| use std::str::FromStr;
 | |
| 
 | |
| use serde::{Deserialize, Serialize};
 | |
| use unicode_width::UnicodeWidthChar;
 | |
| 
 | |
| use ruff_cache::{CacheKey, CacheKeyHasher};
 | |
| use ruff_macros::CacheKey;
 | |
| use ruff_text_size::TextSize;
 | |
| 
 | |
| /// The length of a line of text that is considered too long.
 | |
| ///
 | |
| /// The allowed range of values is 1..=320
 | |
| #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
 | |
| #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 | |
| pub struct LineLength(NonZeroU16);
 | |
| 
 | |
| impl LineLength {
 | |
|     /// Maximum allowed value for a valid [`LineLength`]
 | |
|     pub const MAX: u16 = 320;
 | |
| 
 | |
|     /// Return the numeric value for this [`LineLength`]
 | |
|     pub fn value(&self) -> u16 {
 | |
|         self.0.get()
 | |
|     }
 | |
| 
 | |
|     pub fn text_len(&self) -> TextSize {
 | |
|         TextSize::from(u32::from(self.value()))
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Default for LineLength {
 | |
|     fn default() -> Self {
 | |
|         Self(NonZeroU16::new(88).unwrap())
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl CacheKey for LineLength {
 | |
|     fn cache_key(&self, state: &mut CacheKeyHasher) {
 | |
|         state.write_u16(self.0.get());
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Error type returned when parsing a [`LineLength`] from a string fails
 | |
| pub enum ParseLineWidthError {
 | |
|     /// The string could not be parsed as a valid [u16]
 | |
|     ParseError(ParseIntError),
 | |
|     /// The [u16] value of the string is not a valid [LineLength]
 | |
|     TryFromIntError(LineLengthFromIntError),
 | |
| }
 | |
| 
 | |
| impl std::fmt::Debug for ParseLineWidthError {
 | |
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | |
|         std::fmt::Display::fmt(self, f)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl std::fmt::Display for ParseLineWidthError {
 | |
|     fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | |
|         match self {
 | |
|             ParseLineWidthError::ParseError(err) => std::fmt::Display::fmt(err, fmt),
 | |
|             ParseLineWidthError::TryFromIntError(err) => std::fmt::Display::fmt(err, fmt),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Error for ParseLineWidthError {}
 | |
| 
 | |
| impl FromStr for LineLength {
 | |
|     type Err = ParseLineWidthError;
 | |
| 
 | |
|     fn from_str(s: &str) -> Result<Self, Self::Err> {
 | |
|         let value = u16::from_str(s).map_err(ParseLineWidthError::ParseError)?;
 | |
|         let value = Self::try_from(value).map_err(ParseLineWidthError::TryFromIntError)?;
 | |
|         Ok(value)
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Error type returned when converting a u16 to a [`LineLength`] fails
 | |
| #[derive(Clone, Copy, Debug)]
 | |
| pub struct LineLengthFromIntError(pub u16);
 | |
| 
 | |
| impl TryFrom<u16> for LineLength {
 | |
|     type Error = LineLengthFromIntError;
 | |
| 
 | |
|     fn try_from(value: u16) -> Result<Self, Self::Error> {
 | |
|         match NonZeroU16::try_from(value) {
 | |
|             Ok(value) if value.get() <= Self::MAX => Ok(LineLength(value)),
 | |
|             Ok(_) | Err(_) => Err(LineLengthFromIntError(value)),
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl std::fmt::Display for LineLengthFromIntError {
 | |
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | |
|         writeln!(
 | |
|             f,
 | |
|             "The line width must be a value between 1 and {}.",
 | |
|             LineLength::MAX
 | |
|         )
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl From<LineLength> for u16 {
 | |
|     fn from(value: LineLength) -> Self {
 | |
|         value.0.get()
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl From<LineLength> for NonZeroU16 {
 | |
|     fn from(value: LineLength) -> Self {
 | |
|         value.0
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// A measure of the width of a line of text.
 | |
| ///
 | |
| /// This is used to determine if a line is too long.
 | |
| /// It should be compared to a [`LineLength`].
 | |
| #[derive(Clone, Copy, Debug)]
 | |
| pub struct LineWidthBuilder {
 | |
|     /// The width of the line.
 | |
|     width: usize,
 | |
|     /// The column of the line.
 | |
|     /// This is used to calculate the width of tabs.
 | |
|     column: usize,
 | |
|     /// The tab size to use when calculating the width of tabs.
 | |
|     tab_size: TabSize,
 | |
| }
 | |
| 
 | |
| impl Default for LineWidthBuilder {
 | |
|     fn default() -> Self {
 | |
|         Self::new(TabSize::default())
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl PartialEq for LineWidthBuilder {
 | |
|     fn eq(&self, other: &Self) -> bool {
 | |
|         self.width == other.width
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Eq for LineWidthBuilder {}
 | |
| 
 | |
| impl PartialOrd for LineWidthBuilder {
 | |
|     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
 | |
|         Some(self.cmp(other))
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Ord for LineWidthBuilder {
 | |
|     fn cmp(&self, other: &Self) -> std::cmp::Ordering {
 | |
|         self.width.cmp(&other.width)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl LineWidthBuilder {
 | |
|     pub fn get(&self) -> usize {
 | |
|         self.width
 | |
|     }
 | |
| 
 | |
|     /// Creates a new `LineWidth` with the given tab size.
 | |
|     pub fn new(tab_size: TabSize) -> Self {
 | |
|         LineWidthBuilder {
 | |
|             width: 0,
 | |
|             column: 0,
 | |
|             tab_size,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn update(mut self, chars: impl Iterator<Item = char>) -> Self {
 | |
|         let tab_size: usize = self.tab_size.as_usize();
 | |
|         for c in chars {
 | |
|             match c {
 | |
|                 '\t' => {
 | |
|                     let tab_offset = tab_size - (self.column % tab_size);
 | |
|                     self.width += tab_offset;
 | |
|                     self.column += tab_offset;
 | |
|                 }
 | |
|                 '\n' | '\r' => {
 | |
|                     self.width = 0;
 | |
|                     self.column = 0;
 | |
|                 }
 | |
|                 _ => {
 | |
|                     self.width += c.width().unwrap_or(0);
 | |
|                     self.column += 1;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     /// Adds the given text to the line width.
 | |
|     #[must_use]
 | |
|     pub fn add_str(self, text: &str) -> Self {
 | |
|         self.update(text.chars())
 | |
|     }
 | |
| 
 | |
|     /// Adds the given character to the line width.
 | |
|     #[must_use]
 | |
|     pub fn add_char(self, c: char) -> Self {
 | |
|         self.update(std::iter::once(c))
 | |
|     }
 | |
| 
 | |
|     /// Adds the given width to the line width.
 | |
|     /// Also adds the given width to the column.
 | |
|     /// It is generally better to use [`LineWidthBuilder::add_str`] or [`LineWidthBuilder::add_char`].
 | |
|     /// The width and column should be the same for the corresponding text.
 | |
|     /// Currently, this is only used to add spaces.
 | |
|     #[must_use]
 | |
|     pub fn add_width(mut self, width: usize) -> Self {
 | |
|         self.width += width;
 | |
|         self.column += width;
 | |
|         self
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl PartialEq<LineLength> for LineWidthBuilder {
 | |
|     fn eq(&self, other: &LineLength) -> bool {
 | |
|         self.width == (other.value() as usize)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl PartialOrd<LineLength> for LineWidthBuilder {
 | |
|     fn partial_cmp(&self, other: &LineLength) -> Option<std::cmp::Ordering> {
 | |
|         self.width.partial_cmp(&(other.value() as usize))
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// The size of a tab.
 | |
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
 | |
| #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 | |
| pub struct TabSize(NonZeroU8);
 | |
| 
 | |
| impl TabSize {
 | |
|     pub(crate) fn as_usize(self) -> usize {
 | |
|         self.0.get() as usize
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Default for TabSize {
 | |
|     fn default() -> Self {
 | |
|         Self(NonZeroU8::new(4).unwrap())
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl From<NonZeroU8> for TabSize {
 | |
|     fn from(tab_size: NonZeroU8) -> Self {
 | |
|         Self(tab_size)
 | |
|     }
 | |
| }
 |