-// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
-// file at the top-level directory of this distribution and at
-// http://rust-lang.org/COPYRIGHT.
-//
-// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
-// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
-// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
-// option. This file may not be copied, modified, or distributed
-// except according to those terms.
-
-use config::config_type::ConfigType;
-use config::lists::*;
-use config::{Config, FileName};
-
-use atty;
-
-use std::collections::HashSet;
+use std::collections::{hash_set, HashSet};
+use std::fmt;
use std::path::{Path, PathBuf};
+use std::str::FromStr;
-/// Macro that will stringify the enum variants or a provided textual repr
-#[macro_export]
-macro_rules! configuration_option_enum_stringify {
- ($variant:ident) => {
- stringify!($variant)
- };
-
- ($_variant:ident: $value:expr) => {
- stringify!($value)
- };
-}
-
-/// Macro for deriving implementations of Serialize/Deserialize for enums
-#[macro_export]
-macro_rules! impl_enum_serialize_and_deserialize {
- ( $e:ident, $( $variant:ident $(: $value:expr)* ),* ) => {
- impl ::serde::ser::Serialize for $e {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where S: ::serde::ser::Serializer
- {
- use serde::ser::Error;
-
- // We don't know whether the user of the macro has given us all options.
- #[allow(unreachable_patterns)]
- match *self {
- $(
- $e::$variant => serializer.serialize_str(
- configuration_option_enum_stringify!($variant $(: $value)*)
- ),
- )*
- _ => {
- Err(S::Error::custom(format!("Cannot serialize {:?}", self)))
- }
- }
- }
- }
-
- impl<'de> ::serde::de::Deserialize<'de> for $e {
- fn deserialize<D>(d: D) -> Result<Self, D::Error>
- where D: ::serde::Deserializer<'de> {
- use serde::de::{Error, Visitor};
- use std::marker::PhantomData;
- use std::fmt;
- struct StringOnly<T>(PhantomData<T>);
- impl<'de, T> Visitor<'de> for StringOnly<T>
- where T: ::serde::Deserializer<'de> {
- type Value = String;
- fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
- formatter.write_str("string")
- }
- fn visit_str<E>(self, value: &str) -> Result<String, E> {
- Ok(String::from(value))
- }
- }
- let s = d.deserialize_string(StringOnly::<D>(PhantomData))?;
- $(
- if configuration_option_enum_stringify!($variant $(: $value)*)
- .eq_ignore_ascii_case(&s) {
- return Ok($e::$variant);
- }
- )*
- static ALLOWED: &'static[&str] = &[
- $(configuration_option_enum_stringify!($variant $(: $value)*),)*];
- Err(D::Error::unknown_variant(&s, ALLOWED))
- }
- }
-
- impl ::std::str::FromStr for $e {
- type Err = &'static str;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- $(
- if configuration_option_enum_stringify!($variant $(: $value)*)
- .eq_ignore_ascii_case(s) {
- return Ok($e::$variant);
- }
- )*
- Err("Bad variant")
- }
- }
-
- impl ConfigType for $e {
- fn doc_hint() -> String {
- let mut variants = Vec::new();
- $(
- variants.push(
- configuration_option_enum_stringify!($variant $(: $value)*)
- );
- )*
- format!("[{}]", variants.join("|"))
- }
- }
- };
-}
-
-macro_rules! configuration_option_enum {
- ($e:ident: $( $name:ident $(: $value:expr)* ),+ $(,)*) => (
- #[derive(Copy, Clone, Eq, PartialEq)]
- pub enum $e {
- $( $name ),+
- }
-
- impl ::std::fmt::Debug for $e {
- fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
- f.write_str(match self {
- $(
- $e::$name => configuration_option_enum_stringify!($name $(: $value)*),
- )+
- })
- }
- }
-
- impl_enum_serialize_and_deserialize!($e, $( $name $(: $value)* ),+);
- );
-}
+use itertools::Itertools;
+use rustfmt_config_proc_macro::config_type;
+use serde::de::{SeqAccess, Visitor};
+use serde::ser::SerializeSeq;
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
-configuration_option_enum! { NewlineStyle:
- Auto, // Auto-detect based on the raw source input
- Windows, // \r\n
- Unix, // \n
- Native, // \r\n in Windows, \n on other platforms
-}
-
-impl NewlineStyle {
- fn auto_detect(raw_input_text: &str) -> NewlineStyle {
- if let Some(pos) = raw_input_text.find('\n') {
- let pos = pos.saturating_sub(1);
- if let Some('\r') = raw_input_text.chars().nth(pos) {
- NewlineStyle::Windows
- } else {
- NewlineStyle::Unix
- }
- } else {
- NewlineStyle::Native
- }
- }
+use crate::config::lists::*;
+use crate::config::Config;
- fn native() -> NewlineStyle {
- if cfg!(windows) {
- NewlineStyle::Windows
- } else {
- NewlineStyle::Unix
- }
- }
-
- /// Apply this newline style to the formatted text. When the style is set
- /// to `Auto`, the `raw_input_text` is used to detect the existing line
- /// endings.
- ///
- /// If the style is set to `Auto` and `raw_input_text` contains no
- /// newlines, the `Native` style will be used.
- pub(crate) fn apply(self, formatted_text: &mut String, raw_input_text: &str) {
- use NewlineStyle::*;
- let mut style = self;
- if style == Auto {
- style = Self::auto_detect(raw_input_text);
- }
- if style == Native {
- style = Self::native();
- }
- match style {
- Windows => {
- let mut transformed = String::with_capacity(2 * formatted_text.capacity());
- for c in formatted_text.chars() {
- match c {
- '\n' => transformed.push_str("\r\n"),
- '\r' => continue,
- c => transformed.push(c),
- }
- }
- *formatted_text = transformed;
- }
- Unix => return,
- Native => unreachable!("NewlineStyle::Native"),
- Auto => unreachable!("NewlineStyle::Auto"),
- }
- }
+#[config_type]
+pub enum NewlineStyle {
+ /// Auto-detect based on the raw source input.
+ Auto,
+ /// Force CRLF (`\r\n`).
+ Windows,
+ /// Force CR (`\n).
+ Unix,
+ /// `\r\n` in Windows, `\n`` on other platforms.
+ Native,
}
-configuration_option_enum! { BraceStyle:
+#[config_type]
+/// Where to put the opening brace of items (`fn`, `impl`, etc.).
+pub enum BraceStyle {
+ /// Put the opening brace on the next line.
AlwaysNextLine,
+ /// Put the opening brace on the same line, if possible.
PreferSameLine,
- // Prefer same line except where there is a where clause, in which case force
- // the brace to the next line.
+ /// Prefer the same line except where there is a where-clause, in which
+ /// case force the brace to be put on the next line.
SameLineWhere,
}
-configuration_option_enum! { ControlBraceStyle:
- // K&R style, Rust community default
+#[config_type]
+/// Where to put the opening brace of conditional expressions (`if`, `match`, etc.).
+pub enum ControlBraceStyle {
+ /// K&R style, Rust community default
AlwaysSameLine,
- // Stroustrup style
+ /// Stroustrup style
ClosingNextLine,
- // Allman style
+ /// Allman style
AlwaysNextLine,
}
-configuration_option_enum! { IndentStyle:
- // First line on the same line as the opening brace, all lines aligned with
- // the first line.
+#[config_type]
+/// How to indent.
+pub enum IndentStyle {
+ /// First line on the same line as the opening brace, all lines aligned with
+ /// the first line.
Visual,
- // First line is on a new line and all lines align with block indent.
+ /// First line is on a new line and all lines align with **block** indent.
Block,
}
-configuration_option_enum! { Density:
- // Fit as much on one line as possible.
+#[config_type]
+/// How to place a list-like items.
+/// FIXME: Issue-3581: this should be renamed to ItemsLayout when publishing 2.0
+pub enum Density {
+ /// Fit as much on one line as possible.
Compressed,
- // Use more lines.
+ /// Items are placed horizontally if sufficient space, vertically otherwise.
Tall,
- // Place every item on a separate line.
+ /// Place every item on a separate line.
Vertical,
}
-configuration_option_enum! { TypeDensity:
- // No spaces around "=" and "+"
+#[config_type]
+/// Spacing around type combinators.
+pub enum TypeDensity {
+ /// No spaces around "=" and "+"
Compressed,
- // Spaces around " = " and " + "
+ /// Spaces around " = " and " + "
Wide,
}
-configuration_option_enum! { Heuristics:
- // Turn off any heuristics
+#[config_type]
+/// To what extent does rustfmt pursue its heuristics?
+pub enum Heuristics {
+ /// Turn off any heuristics
Off,
- // Turn on max heuristics
+ /// Turn on max heuristics
Max,
- // Use Rustfmt's defaults
+ /// Use Rustfmt's defaults
Default,
}
impl Density {
- pub fn to_list_tactic(self) -> ListTactic {
+ pub fn to_list_tactic(self, len: usize) -> ListTactic {
match self {
Density::Compressed => ListTactic::Mixed,
Density::Tall => ListTactic::HorizontalVertical,
+ Density::Vertical if len == 1 => ListTactic::Horizontal,
Density::Vertical => ListTactic::Vertical,
}
}
}
-configuration_option_enum! { ReportTactic:
+#[config_type]
+/// Configuration for import groups, i.e. sets of imports separated by newlines.
+pub enum GroupImportsTactic {
+ /// Keep groups as they are.
+ Preserve,
+ /// Discard existing groups, and create new groups for
+ /// 1. `std` / `core` / `alloc` imports
+ /// 2. other imports
+ /// 3. `self` / `crate` / `super` imports
+ StdExternalCrate,
+}
+
+#[config_type]
+/// How to merge imports.
+pub enum ImportGranularity {
+ /// Do not merge imports.
+ Preserve,
+ /// Use one `use` statement per crate.
+ Crate,
+ /// Use one `use` statement per module.
+ Module,
+ /// Use one `use` statement per imported item.
+ Item,
+}
+
+#[config_type]
+pub enum ReportTactic {
Always,
Unnumbered,
Never,
}
-// What Rustfmt should emit. Mostly corresponds to the `--emit` command line
-// option.
-configuration_option_enum! { EmitMode:
- // Emits to files.
+/// What Rustfmt should emit. Mostly corresponds to the `--emit` command line
+/// option.
+#[config_type]
+pub enum EmitMode {
+ /// Emits to files.
Files,
- // Writes the output to stdout.
+ /// Writes the output to stdout.
Stdout,
- // Displays how much of the input file was processed
+ /// Displays how much of the input file was processed
Coverage,
- // Unfancy stdout
+ /// Unfancy stdout
Checkstyle,
- // Output the changed lines (for internal value only)
+ /// Writes the resulting diffs in a JSON format. Returns an empty array
+ /// `[]` if there were no diffs.
+ Json,
+ /// Output the changed lines (for internal value only)
ModifiedLines,
- // Checks if a diff can be generated. If so, rustfmt outputs a diff and quits with exit code 1.
- // This option is designed to be run in CI where a non-zero exit signifies non-standard code
- // formatting. Used for `--check`.
+ /// Checks if a diff can be generated. If so, rustfmt outputs a diff and
+ /// quits with exit code 1.
+ /// This option is designed to be run in CI where a non-zero exit signifies
+ /// non-standard code formatting. Used for `--check`.
Diff,
}
-// Client-preference for coloured output.
-configuration_option_enum! { Color:
- // Always use color, whether it is a piped or terminal output
+/// Client-preference for coloured output.
+#[config_type]
+pub enum Color {
+ /// Always use color, whether it is a piped or terminal output
Always,
- // Never use color
+ /// Never use color
Never,
- // Automatically use color, if supported by terminal
+ /// Automatically use color, if supported by terminal
Auto,
}
-configuration_option_enum! { Version:
- // 1.x.y
+#[config_type]
+/// rustfmt format style version.
+pub enum Version {
+ /// 1.x.y. When specified, rustfmt will format in the same style as 1.0.0.
One,
- // 2.x.y
+ /// 2.x.y. When specified, rustfmt will format in the the latest style.
Two,
}
/// Whether we should use a coloured terminal.
pub fn use_colored_tty(self) -> bool {
match self {
- Color::Always => true,
+ Color::Always | Color::Auto => true,
Color::Never => false,
- Color::Auto => atty::is(atty::Stream::Stdout),
}
}
}
-// How chatty should Rustfmt be?
-configuration_option_enum! { Verbosity:
- // Emit more.
+/// How chatty should Rustfmt be?
+#[config_type]
+pub enum Verbosity {
+ /// Emit more.
Verbose,
+ /// Default.
Normal,
- // Emit as little as possible.
+ /// Emit as little as possible.
Quiet,
}
pub single_line_if_else_max_width: usize,
}
+impl fmt::Display for WidthHeuristics {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{:?}", self)
+ }
+}
+
impl WidthHeuristics {
// Using this WidthHeuristics means we ignore heuristics.
pub fn null() -> WidthHeuristics {
}
/// A set of directories, files and modules that rustfmt should ignore.
-#[derive(Default, Deserialize, Serialize, Clone, Debug, PartialEq)]
-pub struct IgnoreList(HashSet<PathBuf>);
+#[derive(Default, Clone, Debug, PartialEq)]
+pub struct IgnoreList {
+ /// A set of path specified in rustfmt.toml.
+ path_set: HashSet<PathBuf>,
+ /// A path to rustfmt.toml.
+ rustfmt_toml_path: PathBuf,
+}
-impl IgnoreList {
- pub fn add_prefix(&mut self, dir: &Path) {
- self.0 = self
- .0
- .iter()
- .map(|s| {
- if s.has_root() {
- s.clone()
- } else {
- let mut path = PathBuf::from(dir);
- path.push(s);
- path
- }
- })
- .collect();
+impl fmt::Display for IgnoreList {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "[{}]",
+ self.path_set
+ .iter()
+ .format_with(", ", |path, f| f(&format_args!(
+ "{}",
+ path.to_string_lossy()
+ )))
+ )
}
+}
- fn skip_file_inner(&self, file: &Path) -> bool {
- self.0.iter().any(|path| file.starts_with(path))
+impl Serialize for IgnoreList {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut seq = serializer.serialize_seq(Some(self.path_set.len()))?;
+ for e in &self.path_set {
+ seq.serialize_element(e)?;
+ }
+ seq.end()
}
+}
- pub fn skip_file(&self, file: &FileName) -> bool {
- if let FileName::Real(ref path) = file {
- self.skip_file_inner(path)
- } else {
- false
+impl<'de> Deserialize<'de> for IgnoreList {
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct HashSetVisitor;
+ impl<'v> Visitor<'v> for HashSetVisitor {
+ type Value = HashSet<PathBuf>;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a sequence of path")
+ }
+
+ fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'v>,
+ {
+ let mut path_set = HashSet::new();
+ while let Some(elem) = seq.next_element()? {
+ path_set.insert(elem);
+ }
+ Ok(path_set)
+ }
}
+ Ok(IgnoreList {
+ path_set: deserializer.deserialize_seq(HashSetVisitor)?,
+ rustfmt_toml_path: PathBuf::new(),
+ })
+ }
+}
+
+impl<'a> IntoIterator for &'a IgnoreList {
+ type Item = &'a PathBuf;
+ type IntoIter = hash_set::Iter<'a, PathBuf>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.path_set.iter()
}
}
-impl ::std::str::FromStr for IgnoreList {
+impl IgnoreList {
+ pub fn add_prefix(&mut self, dir: &Path) {
+ self.rustfmt_toml_path = dir.to_path_buf();
+ }
+
+ pub fn rustfmt_toml_path(&self) -> &Path {
+ &self.rustfmt_toml_path
+ }
+}
+
+impl FromStr for IgnoreList {
type Err = &'static str;
fn from_str(_: &str) -> Result<Self, Self::Err> {
fn config_path(&self) -> Option<&Path>;
}
-/// The edition of the compiler (RFC 2052)
-configuration_option_enum! { Edition:
- Edition2015: 2015,
- Edition2018: 2018,
+/// The edition of the syntax and semntics of code (RFC 2052).
+#[config_type]
+pub enum Edition {
+ #[value = "2015"]
+ #[doc_hint = "2015"]
+ /// Edition 2015.
+ Edition2015,
+ #[value = "2018"]
+ #[doc_hint = "2018"]
+ /// Edition 2018.
+ Edition2018,
+ #[value = "2021"]
+ #[doc_hint = "2021"]
+ /// Edition 2021.
+ Edition2021,
}
impl Default for Edition {
}
}
-impl Edition {
- pub(crate) fn to_libsyntax_pos_edition(self) -> syntax_pos::edition::Edition {
- match self {
- Edition::Edition2015 => syntax_pos::edition::Edition::Edition2015,
- Edition::Edition2018 => syntax_pos::edition::Edition::Edition2018,
+impl From<Edition> for rustc_span::edition::Edition {
+ fn from(edition: Edition) -> Self {
+ match edition {
+ Edition::Edition2015 => Self::Edition2015,
+ Edition::Edition2018 => Self::Edition2018,
+ Edition::Edition2021 => Self::Edition2021,
}
}
}
-#[test]
-fn test_newline_style_auto_detect() {
- let lf = "One\nTwo\nThree";
- let crlf = "One\r\nTwo\r\nThree";
- let none = "One Two Three";
-
- assert_eq!(NewlineStyle::Unix, NewlineStyle::auto_detect(lf));
- assert_eq!(NewlineStyle::Windows, NewlineStyle::auto_detect(crlf));
- assert_eq!(NewlineStyle::Native, NewlineStyle::auto_detect(none));
-}
-
-#[test]
-fn test_newline_style_auto_apply() {
- let auto = NewlineStyle::Auto;
-
- let formatted_text = "One\nTwo\nThree";
- let raw_input_text = "One\nTwo\nThree";
-
- let mut out = String::from(formatted_text);
- auto.apply(&mut out, raw_input_text);
- assert_eq!("One\nTwo\nThree", &out, "auto should detect 'lf'");
-
- let formatted_text = "One\nTwo\nThree";
- let raw_input_text = "One\r\nTwo\r\nThree";
-
- let mut out = String::from(formatted_text);
- auto.apply(&mut out, raw_input_text);
- assert_eq!("One\r\nTwo\r\nThree", &out, "auto should detect 'crlf'");
-
- #[cfg(not(windows))]
- {
- let formatted_text = "One\nTwo\nThree";
- let raw_input_text = "One Two Three";
-
- let mut out = String::from(formatted_text);
- auto.apply(&mut out, raw_input_text);
- assert_eq!(
- "One\nTwo\nThree", &out,
- "auto-native-unix should detect 'lf'"
- );
+impl PartialOrd for Edition {
+ fn partial_cmp(&self, other: &Edition) -> Option<std::cmp::Ordering> {
+ rustc_span::edition::Edition::partial_cmp(&(*self).into(), &(*other).into())
}
+}
- #[cfg(windows)]
- {
- let formatted_text = "One\nTwo\nThree";
- let raw_input_text = "One Two Three";
-
- let mut out = String::from(formatted_text);
- auto.apply(&mut out, raw_input_text);
- assert_eq!(
- "One\r\nTwo\r\nThree", &out,
- "auto-native-windows should detect 'crlf'"
- );
- }
+/// Controls how rustfmt should handle leading pipes on match arms.
+#[config_type]
+pub enum MatchArmLeadingPipe {
+ /// Place leading pipes on all match arms
+ Always,
+ /// Never emit leading pipes on match arms
+ Never,
+ /// Preserve any existing leading pipes
+ Preserve,
}