1 // Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use config::file_lines::FileLines;
12 use config::options::{IgnoreList, WidthHeuristics};
14 /// Trait for types that can be used in `Config`.
15 pub trait ConfigType: Sized {
16 /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a
17 /// pipe-separated list of variants; for other types it returns "<type>".
18 fn doc_hint() -> String;
21 impl ConfigType for bool {
22 fn doc_hint() -> String {
23 String::from("<boolean>")
27 impl ConfigType for usize {
28 fn doc_hint() -> String {
29 String::from("<unsigned integer>")
33 impl ConfigType for isize {
34 fn doc_hint() -> String {
35 String::from("<signed integer>")
39 impl ConfigType for String {
40 fn doc_hint() -> String {
41 String::from("<string>")
45 impl ConfigType for FileLines {
46 fn doc_hint() -> String {
47 String::from("<json>")
51 impl ConfigType for WidthHeuristics {
52 fn doc_hint() -> String {
57 impl ConfigType for IgnoreList {
58 fn doc_hint() -> String {
59 String::from("[<string>,..]")
63 /// Check if we're in a nightly build.
65 /// The environment variable `CFG_RELEASE_CHANNEL` is set during the rustc bootstrap
66 /// to "stable", "beta", or "nightly" depending on what toolchain is being built.
67 /// If we are being built as part of the stable or beta toolchains, we want
68 /// to disable unstable configuration options.
70 /// If we're being built by cargo (e.g. `cargo +nightly install rustfmt-nightly`),
71 /// `CFG_RELEASE_CHANNEL` is not set. As we only support being built against the
72 /// nightly compiler when installed from crates.io, default to nightly mode.
73 macro_rules! is_nightly_channel {
75 option_env!("CFG_RELEASE_CHANNEL")
76 .map(|c| c == "nightly" || c == "dev")
81 macro_rules! create_config {
82 ($($i:ident: $ty:ty, $def:expr, $stb:expr, $( $dstring:expr ),+ );+ $(;)*) => (
84 use std::collections::HashSet;
89 // if a license_template_path has been specified, successfully read, parsed and compiled
90 // into a regex, it will be stored here
91 pub license_template: Option<Regex>,
92 // For each config item, we store a bool indicating whether it has
93 // been accessed and the value, and a bool whether the option was
94 // manually initialised, or taken from the default,
95 $($i: (Cell<bool>, bool, $ty, bool)),+
98 // Just like the Config struct but with each property wrapped
99 // as Option<T>. This is used to parse a rustfmt.toml that doesn't
100 // specify all properties of `Config`.
101 // We first parse into `PartialConfig`, then create a default `Config`
102 // and overwrite the properties with corresponding values from `PartialConfig`.
103 #[derive(Deserialize, Serialize, Clone)]
104 pub struct PartialConfig {
105 $(pub $i: Option<$ty>),+
109 pub fn to_toml(&self) -> Result<String, String> {
110 // Non-user-facing options can't be specified in TOML
111 let mut cloned = self.clone();
112 cloned.file_lines = None;
113 cloned.verbose = None;
114 cloned.width_heuristics = None;
116 ::toml::to_string(&cloned)
117 .map_err(|e| format!("Could not output config: {}", e.to_string()))
121 // Macro hygiene won't allow us to make `set_$i()` methods on Config
122 // for each item, so this struct is used to give the API to set values:
123 // `config.set().option(false)`. It's pretty ugly. Consider replacing
124 // with `config.set_option(false)` if we ever get a stable/usable
125 // `concat_idents!()`.
126 pub struct ConfigSetter<'a>(&'a mut Config);
128 impl<'a> ConfigSetter<'a> {
130 pub fn $i(&mut self, value: $ty) {
131 (self.0).$i.2 = value;
132 match stringify!($i) {
133 "max_width" | "use_small_heuristics" => self.0.set_heuristics(),
134 "license_template_path" => self.0.set_license_template(),
141 // Query each option, returns true if the user set the option, false if
142 // a default was used.
143 pub struct ConfigWasSet<'a>(&'a Config);
145 impl<'a> ConfigWasSet<'a> {
147 pub fn $i(&self) -> bool {
154 pub(crate) fn version_meets_requirement(&self) -> bool {
155 if self.was_set().required_version() {
156 let version = env!("CARGO_PKG_VERSION");
157 let required_version = self.required_version();
158 if version != required_version {
160 "Error: rustfmt version ({}) doesn't match the required version ({})",
172 pub fn $i(&self) -> $ty {
178 pub fn set(&mut self) -> ConfigSetter {
182 pub fn was_set(&self) -> ConfigWasSet {
186 fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
188 if let Some(val) = parsed.$i {
193 if is_nightly_channel!() {
197 eprintln!("Warning: can't set `{} = {:?}`, unstable features are only \
198 available in nightly channel.", stringify!($i), val);
203 self.set_heuristics();
204 self.set_license_template();
205 self.set_ignore(dir);
209 /// Returns a hash set initialized with every user-facing config option name.
211 pub(crate) fn hash_set() -> HashSet<String> {
212 let mut hash_set = HashSet::new();
214 hash_set.insert(stringify!($i).to_owned());
219 pub(crate) fn is_valid_name(name: &str) -> bool {
222 stringify!($i) => true,
228 pub(crate) fn from_toml(toml: &str, dir: &Path) -> Result<Config, String> {
229 let parsed: ::toml::Value =
230 toml.parse().map_err(|e| format!("Could not parse TOML: {}", e))?;
231 let mut err: String = String::new();
235 .ok_or(String::from("Parsed config was not table"))?;
236 for key in table.keys() {
237 if !Config::is_valid_name(key) {
238 let msg = &format!("Warning: Unknown configuration option `{}`\n", key);
243 match parsed.try_into() {
244 Ok(parsed_config) => {
248 Ok(Config::default().fill_from_parsed_config(parsed_config, dir: &Path))
251 err.push_str("Error: Decoding config file failed:\n");
252 err.push_str(format!("{}\n", e).as_str());
253 err.push_str("Please check your config file.");
259 pub fn used_options(&self) -> PartialConfig {
262 $i: if self.$i.0.get() {
263 Some(self.$i.2.clone())
271 pub fn all_options(&self) -> PartialConfig {
274 $i: Some(self.$i.2.clone()),
279 pub fn override_value(&mut self, key: &str, val: &str)
285 self.$i.2 = val.parse::<$ty>()
286 .expect(&format!("Failed to parse override for {} (\"{}\") as a {}",
292 _ => panic!("Unknown config key in override: {}", key)
296 "max_width" | "use_small_heuristics" => self.set_heuristics(),
297 "license_template_path" => self.set_license_template(),
302 /// Construct a `Config` from the toml file specified at `file_path`.
304 /// This method only looks at the provided path, for a method that
305 /// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`.
307 /// Return a `Config` if the config could be read and parsed from
308 /// the file, Error otherwise.
309 pub(super) fn from_toml_path(file_path: &Path) -> Result<Config, Error> {
310 let mut file = File::open(&file_path)?;
311 let mut toml = String::new();
312 file.read_to_string(&mut toml)?;
313 Config::from_toml(&toml, file_path.parent().unwrap())
314 .map_err(|err| Error::new(ErrorKind::InvalidData, err))
317 /// Resolve the config for input in `dir`.
319 /// Searches for `rustfmt.toml` beginning with `dir`, and
320 /// recursively checking parents of `dir` if no config file is found.
321 /// If no config file exists in `dir` or in any parent, a
322 /// default `Config` will be returned (and the returned path will be empty).
324 /// Returns the `Config` to use, and the path of the project file if there was
326 pub(super) fn from_resolved_toml_path(
328 ) -> Result<(Config, Option<PathBuf>), Error> {
329 /// Try to find a project file in the given directory and its parents.
330 /// Returns the path of a the nearest project file if one exists,
331 /// or `None` if no project file was found.
332 fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
333 let mut current = if dir.is_relative() {
334 env::current_dir()?.join(dir)
339 current = fs::canonicalize(current)?;
342 match get_toml_path(¤t) {
343 Ok(Some(path)) => return Ok(Some(path)),
344 Err(e) => return Err(e),
348 // If the current directory has no parent, we're done searching.
355 match resolve_project_file(dir)? {
356 None => Ok((Config::default(), None)),
357 Some(path) => Config::from_toml_path(&path).map(|config| (config, Some(path))),
361 pub fn is_hidden_option(name: &str) -> bool {
362 const HIDE_OPTIONS: [&str; 4] =
363 ["verbose", "verbose_diff", "file_lines", "width_heuristics"];
364 HIDE_OPTIONS.contains(&name)
367 pub fn print_docs(out: &mut Write, include_unstable: bool) {
370 $( let max = cmp::max(max, stringify!($i).len()+1); )+
371 let mut space_str = String::with_capacity(max);
375 writeln!(out, "Configuration Options:").unwrap();
377 if $stb || include_unstable {
378 let name_raw = stringify!($i);
380 if !Config::is_hidden_option(name_raw) {
381 let mut name_out = String::with_capacity(max);
382 for _ in name_raw.len()..max-1 {
385 name_out.push_str(name_raw);
388 "{}{} Default: {:?}{}",
392 if !$stb { " (unstable)" } else { "" }).unwrap();
394 writeln!(out, "{}{}", space_str, $dstring).unwrap();
396 writeln!(out).unwrap();
402 fn set_heuristics(&mut self) {
403 if self.use_small_heuristics.2 == Heuristics::Default {
404 let max_width = self.max_width.2;
405 self.set().width_heuristics(WidthHeuristics::scaled(max_width));
406 } else if self.use_small_heuristics.2 == Heuristics::Max {
407 let max_width = self.max_width.2;
408 self.set().width_heuristics(WidthHeuristics::set(max_width));
410 self.set().width_heuristics(WidthHeuristics::null());
414 fn set_license_template(&mut self) {
415 if self.was_set().license_template_path() {
416 let lt_path = self.license_template_path();
417 match license::load_and_compile_template(<_path) {
418 Ok(re) => self.license_template = Some(re),
419 Err(msg) => eprintln!("Warning for license template file {:?}: {}",
425 fn set_ignore(&mut self, dir: &Path) {
426 self.ignore.2.add_prefix(dir);
429 /// Returns true if the config key was explicitely set and is the default value.
430 pub fn is_default(&self, key: &str) -> bool {
432 if let stringify!($i) = key {
433 return self.$i.1 && self.$i.2 == $def;
440 // Template for the default configuration
441 impl Default for Config {
442 fn default() -> Config {
444 license_template: None,
446 $i: (Cell::new(false), false, $def, $stb),