1 use std::collections::{hash_set, HashSet}; 2 use std::fmt; 3 use std::path::{Path, PathBuf}; 4 use std::str::FromStr; 5 6 use itertools::Itertools; 7 use rustfmt_config_proc_macro::config_type; 8 use serde::de::{SeqAccess, Visitor}; 9 use serde::ser::SerializeSeq; 10 use serde::{Deserialize, Deserializer, Serialize, Serializer}; 11 12 use crate::config::lists::*; 13 use crate::config::Config; 14 15 #[config_type] 16 pub enum NewlineStyle { 17 /// Auto-detect based on the raw source input. 18 Auto, 19 /// Force CRLF (`\r\n`). 20 Windows, 21 /// Force CR (`\n`). 22 Unix, 23 /// `\r\n` in Windows, `\n` on other platforms. 24 Native, 25 } 26 27 #[config_type] 28 /// Where to put the opening brace of items (`fn`, `impl`, etc.). 29 pub enum BraceStyle { 30 /// Put the opening brace on the next line. 31 AlwaysNextLine, 32 /// Put the opening brace on the same line, if possible. 33 PreferSameLine, 34 /// Prefer the same line except where there is a where-clause, in which 35 /// case force the brace to be put on the next line. 36 SameLineWhere, 37 } 38 39 #[config_type] 40 /// Where to put the opening brace of conditional expressions (`if`, `match`, etc.). 41 pub enum ControlBraceStyle { 42 /// K&R style, Rust community default 43 AlwaysSameLine, 44 /// Stroustrup style 45 ClosingNextLine, 46 /// Allman style 47 AlwaysNextLine, 48 } 49 50 #[config_type] 51 /// How to indent. 52 pub enum IndentStyle { 53 /// First line on the same line as the opening brace, all lines aligned with 54 /// the first line. 55 Visual, 56 /// First line is on a new line and all lines align with **block** indent. 57 Block, 58 } 59 60 #[config_type] 61 /// How to place a list-like items. 62 /// FIXME: Issue-3581: this should be renamed to ItemsLayout when publishing 2.0 63 pub enum Density { 64 /// Fit as much on one line as possible. 65 Compressed, 66 /// Items are placed horizontally if sufficient space, vertically otherwise. 67 Tall, 68 /// Place every item on a separate line. 69 Vertical, 70 } 71 72 #[config_type] 73 /// Spacing around type combinators. 74 pub enum TypeDensity { 75 /// No spaces around "=" and "+" 76 Compressed, 77 /// Spaces around " = " and " + " 78 Wide, 79 } 80 81 #[config_type] 82 /// Heuristic settings that can be used to simply 83 /// the configuration of the granular width configurations 84 /// like `struct_lit_width`, `array_width`, etc. 85 pub enum Heuristics { 86 /// Turn off any heuristics 87 Off, 88 /// Turn on max heuristics 89 Max, 90 /// Use scaled values based on the value of `max_width` 91 Default, 92 } 93 94 impl Density { to_list_tactic(self, len: usize) -> ListTactic95 pub fn to_list_tactic(self, len: usize) -> ListTactic { 96 match self { 97 Density::Compressed => ListTactic::Mixed, 98 Density::Tall => ListTactic::HorizontalVertical, 99 Density::Vertical if len == 1 => ListTactic::Horizontal, 100 Density::Vertical => ListTactic::Vertical, 101 } 102 } 103 } 104 105 #[config_type] 106 /// Configuration for import groups, i.e. sets of imports separated by newlines. 107 pub enum GroupImportsTactic { 108 /// Keep groups as they are. 109 Preserve, 110 /// Discard existing groups, and create new groups for 111 /// 1. `std` / `core` / `alloc` imports 112 /// 2. other imports 113 /// 3. `self` / `crate` / `super` imports 114 StdExternalCrate, 115 /// Discard existing groups, and create a single group for everything 116 One, 117 } 118 119 #[config_type] 120 /// How to merge imports. 121 pub enum ImportGranularity { 122 /// Do not merge imports. 123 Preserve, 124 /// Use one `use` statement per crate. 125 Crate, 126 /// Use one `use` statement per module. 127 Module, 128 /// Use one `use` statement per imported item. 129 Item, 130 /// Use one `use` statement including all items. 131 One, 132 } 133 134 /// Controls how rustfmt should handle case in hexadecimal literals. 135 #[config_type] 136 pub enum HexLiteralCase { 137 /// Leave the literal as-is 138 Preserve, 139 /// Ensure all literals use uppercase lettering 140 Upper, 141 /// Ensure all literals use lowercase lettering 142 Lower, 143 } 144 145 #[config_type] 146 pub enum ReportTactic { 147 Always, 148 Unnumbered, 149 Never, 150 } 151 152 /// What Rustfmt should emit. Mostly corresponds to the `--emit` command line 153 /// option. 154 #[config_type] 155 pub enum EmitMode { 156 /// Emits to files. 157 Files, 158 /// Writes the output to stdout. 159 Stdout, 160 /// Displays how much of the input file was processed 161 Coverage, 162 /// Unfancy stdout 163 Checkstyle, 164 /// Writes the resulting diffs in a JSON format. Returns an empty array 165 /// `[]` if there were no diffs. 166 Json, 167 /// Output the changed lines (for internal value only) 168 ModifiedLines, 169 /// Checks if a diff can be generated. If so, rustfmt outputs a diff and 170 /// quits with exit code 1. 171 /// This option is designed to be run in CI where a non-zero exit signifies 172 /// non-standard code formatting. Used for `--check`. 173 Diff, 174 } 175 176 /// Client-preference for coloured output. 177 #[config_type] 178 pub enum Color { 179 /// Always use color, whether it is a piped or terminal output 180 Always, 181 /// Never use color 182 Never, 183 /// Automatically use color, if supported by terminal 184 Auto, 185 } 186 187 #[config_type] 188 /// rustfmt format style version. 189 pub enum Version { 190 /// 1.x.y. When specified, rustfmt will format in the same style as 1.0.0. 191 One, 192 /// 2.x.y. When specified, rustfmt will format in the the latest style. 193 Two, 194 } 195 196 impl Color { 197 /// Whether we should use a coloured terminal. use_colored_tty(self) -> bool198 pub fn use_colored_tty(self) -> bool { 199 match self { 200 Color::Always | Color::Auto => true, 201 Color::Never => false, 202 } 203 } 204 } 205 206 /// How chatty should Rustfmt be? 207 #[config_type] 208 pub enum Verbosity { 209 /// Emit more. 210 Verbose, 211 /// Default. 212 Normal, 213 /// Emit as little as possible. 214 Quiet, 215 } 216 217 #[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] 218 pub struct WidthHeuristics { 219 // Maximum width of the args of a function call before falling back 220 // to vertical formatting. 221 pub(crate) fn_call_width: usize, 222 // Maximum width of the args of a function-like attributes before falling 223 // back to vertical formatting. 224 pub(crate) attr_fn_like_width: usize, 225 // Maximum width in the body of a struct lit before falling back to 226 // vertical formatting. 227 pub(crate) struct_lit_width: usize, 228 // Maximum width in the body of a struct variant before falling back 229 // to vertical formatting. 230 pub(crate) struct_variant_width: usize, 231 // Maximum width of an array literal before falling back to vertical 232 // formatting. 233 pub(crate) array_width: usize, 234 // Maximum length of a chain to fit on a single line. 235 pub(crate) chain_width: usize, 236 // Maximum line length for single line if-else expressions. A value 237 // of zero means always break if-else expressions. 238 pub(crate) single_line_if_else_max_width: usize, 239 // Maximum line length for single line let-else statements. A value of zero means 240 // always format the divergent `else` block over multiple lines. 241 pub(crate) single_line_let_else_max_width: usize, 242 } 243 244 impl fmt::Display for WidthHeuristics { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 246 write!(f, "{:?}", self) 247 } 248 } 249 250 impl WidthHeuristics { 251 // Using this WidthHeuristics means we ignore heuristics. null() -> WidthHeuristics252 pub fn null() -> WidthHeuristics { 253 WidthHeuristics { 254 fn_call_width: usize::max_value(), 255 attr_fn_like_width: usize::max_value(), 256 struct_lit_width: 0, 257 struct_variant_width: 0, 258 array_width: usize::max_value(), 259 chain_width: usize::max_value(), 260 single_line_if_else_max_width: 0, 261 single_line_let_else_max_width: 0, 262 } 263 } 264 set(max_width: usize) -> WidthHeuristics265 pub fn set(max_width: usize) -> WidthHeuristics { 266 WidthHeuristics { 267 fn_call_width: max_width, 268 attr_fn_like_width: max_width, 269 struct_lit_width: max_width, 270 struct_variant_width: max_width, 271 array_width: max_width, 272 chain_width: max_width, 273 single_line_if_else_max_width: max_width, 274 single_line_let_else_max_width: max_width, 275 } 276 } 277 278 // scale the default WidthHeuristics according to max_width scaled(max_width: usize) -> WidthHeuristics279 pub fn scaled(max_width: usize) -> WidthHeuristics { 280 const DEFAULT_MAX_WIDTH: usize = 100; 281 let max_width_ratio = if max_width > DEFAULT_MAX_WIDTH { 282 let ratio = max_width as f32 / DEFAULT_MAX_WIDTH as f32; 283 // round to the closest 0.1 284 (ratio * 10.0).round() / 10.0 285 } else { 286 1.0 287 }; 288 WidthHeuristics { 289 fn_call_width: (60.0 * max_width_ratio).round() as usize, 290 attr_fn_like_width: (70.0 * max_width_ratio).round() as usize, 291 struct_lit_width: (18.0 * max_width_ratio).round() as usize, 292 struct_variant_width: (35.0 * max_width_ratio).round() as usize, 293 array_width: (60.0 * max_width_ratio).round() as usize, 294 chain_width: (60.0 * max_width_ratio).round() as usize, 295 single_line_if_else_max_width: (50.0 * max_width_ratio).round() as usize, 296 single_line_let_else_max_width: (50.0 * max_width_ratio).round() as usize, 297 } 298 } 299 } 300 301 impl ::std::str::FromStr for WidthHeuristics { 302 type Err = &'static str; 303 from_str(_: &str) -> Result<Self, Self::Err>304 fn from_str(_: &str) -> Result<Self, Self::Err> { 305 Err("WidthHeuristics is not parsable") 306 } 307 } 308 309 impl Default for EmitMode { default() -> EmitMode310 fn default() -> EmitMode { 311 EmitMode::Files 312 } 313 } 314 315 /// A set of directories, files and modules that rustfmt should ignore. 316 #[derive(Default, Clone, Debug, PartialEq)] 317 pub struct IgnoreList { 318 /// A set of path specified in rustfmt.toml. 319 path_set: HashSet<PathBuf>, 320 /// A path to rustfmt.toml. 321 rustfmt_toml_path: PathBuf, 322 } 323 324 impl fmt::Display for IgnoreList { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 326 write!( 327 f, 328 "[{}]", 329 self.path_set 330 .iter() 331 .format_with(", ", |path, f| f(&format_args!( 332 "{}", 333 path.to_string_lossy() 334 ))) 335 ) 336 } 337 } 338 339 impl Serialize for IgnoreList { serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer,340 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 341 where 342 S: Serializer, 343 { 344 let mut seq = serializer.serialize_seq(Some(self.path_set.len()))?; 345 for e in &self.path_set { 346 seq.serialize_element(e)?; 347 } 348 seq.end() 349 } 350 } 351 352 impl<'de> Deserialize<'de> for IgnoreList { deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>,353 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 354 where 355 D: Deserializer<'de>, 356 { 357 struct HashSetVisitor; 358 impl<'v> Visitor<'v> for HashSetVisitor { 359 type Value = HashSet<PathBuf>; 360 361 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 362 formatter.write_str("a sequence of path") 363 } 364 365 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> 366 where 367 A: SeqAccess<'v>, 368 { 369 let mut path_set = HashSet::new(); 370 while let Some(elem) = seq.next_element()? { 371 path_set.insert(elem); 372 } 373 Ok(path_set) 374 } 375 } 376 Ok(IgnoreList { 377 path_set: deserializer.deserialize_seq(HashSetVisitor)?, 378 rustfmt_toml_path: PathBuf::new(), 379 }) 380 } 381 } 382 383 impl<'a> IntoIterator for &'a IgnoreList { 384 type Item = &'a PathBuf; 385 type IntoIter = hash_set::Iter<'a, PathBuf>; 386 into_iter(self) -> Self::IntoIter387 fn into_iter(self) -> Self::IntoIter { 388 self.path_set.iter() 389 } 390 } 391 392 impl IgnoreList { add_prefix(&mut self, dir: &Path)393 pub fn add_prefix(&mut self, dir: &Path) { 394 self.rustfmt_toml_path = dir.to_path_buf(); 395 } 396 rustfmt_toml_path(&self) -> &Path397 pub fn rustfmt_toml_path(&self) -> &Path { 398 &self.rustfmt_toml_path 399 } 400 } 401 402 impl FromStr for IgnoreList { 403 type Err = &'static str; 404 from_str(_: &str) -> Result<Self, Self::Err>405 fn from_str(_: &str) -> Result<Self, Self::Err> { 406 Err("IgnoreList is not parsable") 407 } 408 } 409 410 /// Maps client-supplied options to Rustfmt's internals, mostly overriding 411 /// values in a config with values from the command line. 412 pub trait CliOptions { apply_to(self, config: &mut Config)413 fn apply_to(self, config: &mut Config); config_path(&self) -> Option<&Path>414 fn config_path(&self) -> Option<&Path>; 415 } 416 417 /// The edition of the syntax and semntics of code (RFC 2052). 418 #[config_type] 419 pub enum Edition { 420 #[value = "2015"] 421 #[doc_hint = "2015"] 422 /// Edition 2015. 423 Edition2015, 424 #[value = "2018"] 425 #[doc_hint = "2018"] 426 /// Edition 2018. 427 Edition2018, 428 #[value = "2021"] 429 #[doc_hint = "2021"] 430 /// Edition 2021. 431 Edition2021, 432 #[value = "2024"] 433 #[doc_hint = "2024"] 434 /// Edition 2024. 435 Edition2024, 436 } 437 438 impl Default for Edition { default() -> Edition439 fn default() -> Edition { 440 Edition::Edition2015 441 } 442 } 443 444 impl From<Edition> for rustc_span::edition::Edition { from(edition: Edition) -> Self445 fn from(edition: Edition) -> Self { 446 match edition { 447 Edition::Edition2015 => Self::Edition2015, 448 Edition::Edition2018 => Self::Edition2018, 449 Edition::Edition2021 => Self::Edition2021, 450 Edition::Edition2024 => Self::Edition2024, 451 } 452 } 453 } 454 455 impl PartialOrd for Edition { partial_cmp(&self, other: &Edition) -> Option<std::cmp::Ordering>456 fn partial_cmp(&self, other: &Edition) -> Option<std::cmp::Ordering> { 457 rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into()) 458 } 459 } 460 461 /// Controls how rustfmt should handle leading pipes on match arms. 462 #[config_type] 463 pub enum MatchArmLeadingPipe { 464 /// Place leading pipes on all match arms 465 Always, 466 /// Never emit leading pipes on match arms 467 Never, 468 /// Preserve any existing leading pipes 469 Preserve, 470 } 471