• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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