1#![feature(rustc_private)]
2
3use anyhow::{Result, format_err};
4
5use io::Error as IoError;
6use thiserror::Error;
7
8use rustfmt_nightly as rustfmt;
9use tracing_subscriber::EnvFilter;
10
11use std::collections::HashMap;
12use std::env;
13use std::fs::File;
14use std::io::{self, Read, Write, stdout};
15use std::path::{Path, PathBuf};
16use std::str::FromStr;
17
18use getopts::{Matches, Options};
19
20use crate::rustfmt::{
21 CliOptions, Color, Config, Edition, EmitMode, FileLines, FileName,
22 FormatReportFormatterBuilder, Input, Session, StyleEdition, Verbosity, Version, load_config,
23};
24
25const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rustfmt/issues/new?labels=bug";
26
27extern crate rustc_driver;
29
30fn main() {
31 rustc_driver::install_ice_hook(BUG_REPORT_URL, |_| ());
32
33 tracing_subscriber::fmt()
34 .with_env_filter(EnvFilter::from_env("RUSTFMT_LOG"))
35 .init();
36 let opts = make_opts();
37
38 let exit_code = match execute(&opts) {
39 Ok(code) => code,
40 Err(e) => {
41 eprintln!("{e:#}");
42 1
43 }
44 };
45 std::io::stdout().flush().unwrap();
47
48 std::process::exit(exit_code);
53}
54
55enum Operation {
57 Format {
59 files: Vec<PathBuf>,
60 minimal_config_path: Option<String>,
61 },
62 Help(HelpOp),
64 Version,
66 ConfigOutputDefault { path: Option<String> },
68 ConfigOutputCurrent { path: Option<String> },
70 Stdin { input: String },
72}
73
74#[derive(Error, Debug)]
76pub enum OperationError {
77 #[error("Unknown help topic: `{0}`.")]
79 UnknownHelpTopic(String),
80 #[error("Unknown print-config option: `{0}`.")]
82 UnknownPrintConfigTopic(String),
83 #[error("The `--print-config=minimal` option doesn't work with standard input.")]
85 MinimalPathWithStdin,
86 #[error("{0}")]
88 IoError(IoError),
89 #[error("Emit mode {0} not supported with standard output.")]
92 StdinBadEmit(EmitMode),
93}
94
95impl From<IoError> for OperationError {
96 fn from(e: IoError) -> OperationError {
97 OperationError::IoError(e)
98 }
99}
100
101enum HelpOp {
103 None,
104 Config,
105 FileLines,
106}
107
108fn make_opts() -> Options {
109 let mut opts = Options::new();
110
111 opts.optflag(
112 "",
113 "check",
114 "Run in 'check' mode. Exits with 0 if input is formatted correctly. Exits \
115 with 1 and prints a diff if formatting is required.",
116 );
117 let is_nightly = is_nightly();
118 let emit_opts = if is_nightly {
119 "[files|stdout|coverage|checkstyle|json]"
120 } else {
121 "[files|stdout]"
122 };
123 opts.optopt("", "emit", "What data to emit and how", emit_opts);
124 opts.optflag("", "backup", "Backup any modified files.");
125 opts.optopt(
126 "",
127 "config-path",
128 "Recursively searches the given path for the rustfmt.toml config file. If not \
129 found reverts to the input file path",
130 "[Path for the configuration file]",
131 );
132 opts.optopt(
133 "",
134 "edition",
135 "Rust edition to use",
136 "[2015|2018|2021|2024]",
137 );
138 opts.optopt(
139 "",
140 "color",
141 "Use colored output (if supported)",
142 "[always|never|auto]",
143 );
144 opts.optopt(
145 "",
146 "print-config",
147 "Dumps a default or minimal config to PATH. A minimal config is the \
148 subset of the current config file used for formatting the current program. \
149 `current` writes to stdout current config as if formatting the file at PATH.",
150 "[default|minimal|current] PATH",
151 );
152 opts.optflag(
153 "l",
154 "files-with-diff",
155 "Prints the names of mismatched files that were formatted. Prints the names of \
156 files that would be formatted when used with `--check` mode. ",
157 );
158 opts.optmulti(
159 "",
160 "config",
161 "Set options from command line. These settings take priority over .rustfmt.toml",
162 "[key1=val1,key2=val2...]",
163 );
164 opts.optopt(
165 "",
166 "style-edition",
167 "The edition of the Style Guide.",
168 "[2015|2018|2021|2024]",
169 );
170
171 if is_nightly {
172 opts.optflag(
173 "",
174 "unstable-features",
175 "Enables unstable features. Only available on nightly channel.",
176 );
177 opts.optopt(
178 "",
179 "file-lines",
180 "Format specified line ranges. Run with `--help=file-lines` for \
181 more detail (unstable).",
182 "JSON",
183 );
184 opts.optflag(
185 "",
186 "error-on-unformatted",
187 "Error if unable to get comments or string literals within max_width, \
188 or they are left with trailing whitespaces (unstable).",
189 );
190 opts.optflag(
191 "",
192 "skip-children",
193 "Don't reformat child modules (unstable).",
194 );
195 }
196
197 opts.optflag("v", "verbose", "Print verbose output");
198 opts.optflag("q", "quiet", "Print less output");
199 opts.optflag("V", "version", "Show version information");
200 let help_topics = if is_nightly {
201 "`config` or `file-lines`"
202 } else {
203 "`config`"
204 };
205 let mut help_topic_msg = "Show this message or help about a specific topic: ".to_owned();
206 help_topic_msg.push_str(help_topics);
207
208 opts.optflagopt("h", "help", &help_topic_msg, "=TOPIC");
209
210 opts
211}
212
213fn is_nightly() -> bool {
214 option_env!("CFG_RELEASE_CHANNEL").map_or(true, |c| c == "nightly" || c == "dev")
215}
216
217fn execute(opts: &Options) -> Result<i32> {
219 let matches = opts.parse(env::args().skip(1))?;
220 let options = GetOptsOptions::from_matches(&matches)?;
221
222 match determine_operation(&matches)? {
223 Operation::Help(HelpOp::None) => {
224 print_usage_to_stdout(opts, "");
225 Ok(0)
226 }
227 Operation::Help(HelpOp::Config) => {
228 Config::print_docs(&mut stdout(), options.unstable_features);
229 Ok(0)
230 }
231 Operation::Help(HelpOp::FileLines) => {
232 print_help_file_lines();
233 Ok(0)
234 }
235 Operation::Version => {
236 print_version();
237 Ok(0)
238 }
239 Operation::ConfigOutputDefault { path } => {
240 let toml = Config::default().all_options().to_toml()?;
241 if let Some(path) = path {
242 let mut file = File::create(path)?;
243 file.write_all(toml.as_bytes())?;
244 } else {
245 io::stdout().write_all(toml.as_bytes())?;
246 }
247 Ok(0)
248 }
249 Operation::ConfigOutputCurrent { path } => {
250 let path = match path {
251 Some(path) => path,
252 None => return Err(format_err!("PATH required for `--print-config current`")),
253 };
254
255 let file = PathBuf::from(path);
256 let file = file.canonicalize().unwrap_or(file);
257
258 let (config, _) = load_config(Some(file.parent().unwrap()), Some(options))?;
259 let toml = config.all_options().to_toml()?;
260 io::stdout().write_all(toml.as_bytes())?;
261
262 Ok(0)
263 }
264 Operation::Stdin { input } => format_string(input, options),
265 Operation::Format {
266 files,
267 minimal_config_path,
268 } => format(files, minimal_config_path, &options),
269 }
270}
271
272fn format_string(input: String, options: GetOptsOptions) -> Result<i32> {
273 let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
275
276 if options.check {
277 config.set_cli().emit_mode(EmitMode::Diff);
278 } else {
279 match options.emit_mode {
280 None => {
283 config
284 .set()
285 .emit_mode(options.emit_mode.unwrap_or(EmitMode::Stdout));
286 }
287 Some(EmitMode::Stdout) | Some(EmitMode::Checkstyle) | Some(EmitMode::Json) => {
288 config
289 .set_cli()
290 .emit_mode(options.emit_mode.unwrap_or(EmitMode::Stdout));
291 }
292 Some(emit_mode) => {
293 return Err(OperationError::StdinBadEmit(emit_mode).into());
294 }
295 }
296 }
297 config.set().verbose(Verbosity::Quiet);
298
299 if options.file_lines.is_all() {
301 config.set().file_lines(options.file_lines);
302 } else {
303 config.set_cli().file_lines(options.file_lines);
304 }
305
306 for f in config.file_lines().files() {
307 match *f {
308 FileName::Stdin => {}
309 _ => eprintln!("Warning: Extra file listed in file_lines option '{f}'"),
310 }
311 }
312
313 let out = &mut stdout();
314 let mut session = Session::new(config, Some(out));
315 format_and_emit_report(&mut session, Input::Text(input));
316
317 let exit_code = if session.has_operational_errors() || session.has_parsing_errors() {
318 1
319 } else {
320 0
321 };
322 Ok(exit_code)
323}
324
325fn format(
326 files: Vec<PathBuf>,
327 minimal_config_path: Option<String>,
328 options: &GetOptsOptions,
329) -> Result<i32> {
330 options.verify_file_lines(&files);
331 let (config, config_path) = load_config(None, Some(options.clone()))?;
332
333 if config.verbose() == Verbosity::Verbose {
334 if let Some(path) = config_path.as_ref() {
335 println!("Using rustfmt config file {}", path.display());
336 }
337 }
338
339 let out = &mut stdout();
340 let mut session = Session::new(config, Some(out));
341
342 for file in files {
343 if !file.exists() {
344 eprintln!("Error: file `{}` does not exist", file.display());
345 session.add_operational_error();
346 } else if file.is_dir() {
347 eprintln!("Error: `{}` is a directory", file.display());
348 session.add_operational_error();
349 } else {
350 if config_path.is_none() {
352 let (local_config, config_path) =
353 load_config(Some(file.parent().unwrap()), Some(options.clone()))?;
354 if local_config.verbose() == Verbosity::Verbose {
355 if let Some(path) = config_path {
356 println!(
357 "Using rustfmt config file {} for {}",
358 path.display(),
359 file.display()
360 );
361 }
362 }
363
364 session.override_config(local_config, |sess| {
365 format_and_emit_report(sess, Input::File(file))
366 });
367 } else {
368 format_and_emit_report(&mut session, Input::File(file));
369 }
370 }
371 }
372
373 if let Some(path) = minimal_config_path {
376 let mut file = File::create(path)?;
377 let toml = session.config.used_options().to_toml()?;
378 file.write_all(toml.as_bytes())?;
379 }
380
381 let exit_code = if session.has_operational_errors()
382 || session.has_parsing_errors()
383 || ((session.has_diff() || session.has_check_errors()) && options.check)
384 {
385 1
386 } else {
387 0
388 };
389 Ok(exit_code)
390}
391
392fn format_and_emit_report<T: Write>(session: &mut Session<'_, T>, input: Input) {
393 match session.format(input) {
394 Ok(report) => {
395 if report.has_warnings() {
396 eprintln!(
397 "{}",
398 FormatReportFormatterBuilder::new(&report)
399 .enable_colors(should_print_with_colors(session))
400 .build()
401 );
402 }
403 }
404 Err(msg) => {
405 eprintln!("Error writing files: {msg}");
406 session.add_operational_error();
407 }
408 }
409}
410
411fn should_print_with_colors<T: Write>(session: &mut Session<'_, T>) -> bool {
412 term::stderr().is_some_and(|t| {
413 session.config.color().use_colored_tty()
414 && t.supports_color()
415 && t.supports_attr(term::Attr::Bold)
416 })
417}
418
419fn print_usage_to_stdout(opts: &Options, reason: &str) {
420 let sep = if reason.is_empty() {
421 String::new()
422 } else {
423 format!("{reason}\n\n")
424 };
425 let msg = format!("{sep}Format Rust code\n\nusage: rustfmt [options] <file>...");
426 println!("{}", opts.usage(&msg));
427}
428
429fn print_help_file_lines() {
430 println!(
431 "If you want to restrict reformatting to specific sets of lines, you can
432use the `--file-lines` option. Its argument is a JSON array of objects
433with `file` and `range` properties, where `file` is a file name, and
434`range` is an array representing a range of lines like `[7,13]`. Ranges
435are 1-based and inclusive of both end points. Specifying an empty array
436will result in no files being formatted. For example,
437
438```
439rustfmt src/lib.rs src/foo.rs --file-lines '[
440 {{\"file\":\"src/lib.rs\",\"range\":[7,13]}},
441 {{\"file\":\"src/lib.rs\",\"range\":[21,29]}},
442 {{\"file\":\"src/foo.rs\",\"range\":[10,11]}},
443 {{\"file\":\"src/foo.rs\",\"range\":[15,15]}}]'
444```
445
446would format lines `7-13` and `21-29` of `src/lib.rs`, and lines `10-11`,
447and `15` of `src/foo.rs`. No other files would be formatted, even if they
448are included as out of line modules from `src/lib.rs`."
449 );
450}
451
452fn print_version() {
453 let version_number = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown");
454 let commit_info = include_str!(concat!(env!("OUT_DIR"), "/commit-info.txt"));
455
456 if commit_info.is_empty() {
457 println!("rustfmt {version_number}");
458 } else {
459 println!("rustfmt {version_number}-{commit_info}");
460 }
461}
462
463fn determine_operation(matches: &Matches) -> Result<Operation, OperationError> {
464 if matches.opt_present("h") {
465 let Some(topic) = matches.opt_str("h") else {
466 return Ok(Operation::Help(HelpOp::None));
467 };
468
469 return match topic.as_str() {
470 "config" => Ok(Operation::Help(HelpOp::Config)),
471 "file-lines" if is_nightly() => Ok(Operation::Help(HelpOp::FileLines)),
472 _ => Err(OperationError::UnknownHelpTopic(topic)),
473 };
474 }
475 let mut free_matches = matches.free.iter();
476
477 let mut minimal_config_path = None;
478 if let Some(kind) = matches.opt_str("print-config") {
479 let path = free_matches.next().cloned();
480 match kind.as_str() {
481 "default" => return Ok(Operation::ConfigOutputDefault { path }),
482 "current" => return Ok(Operation::ConfigOutputCurrent { path }),
483 "minimal" => {
484 minimal_config_path = path;
485 if minimal_config_path.is_none() {
486 eprintln!("WARNING: PATH required for `--print-config minimal`.");
487 }
488 }
489 _ => {
490 return Err(OperationError::UnknownPrintConfigTopic(kind));
491 }
492 }
493 }
494
495 if matches.opt_present("version") {
496 return Ok(Operation::Version);
497 }
498
499 let files: Vec<_> = free_matches
500 .map(|s| {
501 let p = PathBuf::from(s);
502 p.canonicalize().unwrap_or(p)
505 })
506 .collect();
507
508 if files.is_empty() {
510 if minimal_config_path.is_some() {
511 return Err(OperationError::MinimalPathWithStdin);
512 }
513 let mut buffer = String::new();
514 io::stdin().read_to_string(&mut buffer)?;
515
516 return Ok(Operation::Stdin { input: buffer });
517 }
518
519 Ok(Operation::Format {
520 files,
521 minimal_config_path,
522 })
523}
524
525const STABLE_EMIT_MODES: [EmitMode; 3] = [EmitMode::Files, EmitMode::Stdout, EmitMode::Diff];
526
527#[derive(Clone, Debug, Default)]
529struct GetOptsOptions {
530 skip_children: Option<bool>,
531 quiet: bool,
532 verbose: bool,
533 config_path: Option<PathBuf>,
534 inline_config: HashMap<String, String>,
535 emit_mode: Option<EmitMode>,
536 backup: bool,
537 check: bool,
538 edition: Option<Edition>,
539 style_edition: Option<StyleEdition>,
540 color: Option<Color>,
541 file_lines: FileLines, unstable_features: bool,
543 error_on_unformatted: Option<bool>,
544 print_misformatted_file_names: bool,
545}
546
547impl GetOptsOptions {
548 pub fn from_matches(matches: &Matches) -> Result<GetOptsOptions> {
549 let mut options = GetOptsOptions::default();
550 options.verbose = matches.opt_present("verbose");
551 options.quiet = matches.opt_present("quiet");
552 if options.verbose && options.quiet {
553 return Err(format_err!("Can't use both `--verbose` and `--quiet`"));
554 }
555
556 let rust_nightly = is_nightly();
557
558 if rust_nightly {
559 options.unstable_features = matches.opt_present("unstable-features");
560
561 if options.unstable_features {
562 if matches.opt_present("skip-children") {
563 options.skip_children = Some(true);
564 }
565 if matches.opt_present("error-on-unformatted") {
566 options.error_on_unformatted = Some(true);
567 }
568 if let Some(ref file_lines) = matches.opt_str("file-lines") {
569 options.file_lines = file_lines.parse()?;
570 }
571 } else {
572 let mut unstable_options = vec![];
573 if matches.opt_present("skip-children") {
574 unstable_options.push("`--skip-children`");
575 }
576 if matches.opt_present("error-on-unformatted") {
577 unstable_options.push("`--error-on-unformatted`");
578 }
579 if matches.opt_present("file-lines") {
580 unstable_options.push("`--file-lines`");
581 }
582 if !unstable_options.is_empty() {
583 let s = if unstable_options.len() == 1 { "" } else { "s" };
584 return Err(format_err!(
585 "Unstable option{} ({}) used without `--unstable-features`",
586 s,
587 unstable_options.join(", "),
588 ));
589 }
590 }
591 }
592
593 options.config_path = matches.opt_str("config-path").map(PathBuf::from);
594
595 options.inline_config = matches
596 .opt_strs("config")
597 .iter()
598 .flat_map(|config| config.split(','))
599 .map(
600 |key_val| match key_val.char_indices().find(|(_, ch)| *ch == '=') {
601 Some((middle, _)) => {
602 let (key, val) = (&key_val[..middle], &key_val[middle + 1..]);
603 if !Config::is_valid_key_val(key, val) {
604 Err(format_err!("invalid key=val pair: `{}`", key_val))
605 } else {
606 Ok((key.to_string(), val.to_string()))
607 }
608 }
609
610 None => Err(format_err!(
611 "--config expects comma-separated list of key=val pairs, found `{}`",
612 key_val
613 )),
614 },
615 )
616 .collect::<Result<HashMap<_, _>, _>>()?;
617
618 options.check = matches.opt_present("check");
619 if let Some(ref emit_str) = matches.opt_str("emit") {
620 if options.check {
621 return Err(format_err!("Invalid to use `--emit` and `--check`"));
622 }
623
624 options.emit_mode = Some(emit_mode_from_emit_str(emit_str)?);
625 }
626
627 if let Some(ref edition_str) = matches.opt_str("edition") {
628 options.edition = Some(edition_from_edition_str(edition_str)?);
629 }
630
631 if matches.opt_present("backup") {
632 options.backup = true;
633 }
634
635 if matches.opt_present("files-with-diff") {
636 options.print_misformatted_file_names = true;
637 }
638
639 if !rust_nightly {
640 if let Some(ref emit_mode) = options.emit_mode {
641 if !STABLE_EMIT_MODES.contains(emit_mode) {
642 return Err(format_err!(
643 "Invalid value for `--emit` - using an unstable \
644 value without `--unstable-features`",
645 ));
646 }
647 }
648 }
649
650 if let Some(ref color) = matches.opt_str("color") {
651 match Color::from_str(color) {
652 Ok(color) => options.color = Some(color),
653 _ => return Err(format_err!("Invalid color: {}", color)),
654 }
655 }
656
657 if let Some(ref edition_str) = matches.opt_str("style-edition") {
658 options.style_edition = Some(style_edition_from_style_edition_str(edition_str)?);
659 }
660
661 Ok(options)
662 }
663
664 fn verify_file_lines(&self, files: &[PathBuf]) {
665 for f in self.file_lines.files() {
666 match *f {
667 FileName::Real(ref f) if files.contains(f) => {}
668 FileName::Real(_) => {
669 eprintln!("Warning: Extra file listed in file_lines option '{f}'")
670 }
671 FileName::Stdin => eprintln!("Warning: Not a file '{f}'"),
672 }
673 }
674 }
675}
676
677impl CliOptions for GetOptsOptions {
678 fn apply_to(self, config: &mut Config) {
679 if self.verbose {
680 config.set_cli().verbose(Verbosity::Verbose);
681 } else if self.quiet {
682 config.set_cli().verbose(Verbosity::Quiet);
683 } else {
684 config.set().verbose(Verbosity::Normal);
685 }
686
687 if self.file_lines.is_all() {
688 config.set().file_lines(self.file_lines);
689 } else {
690 config.set_cli().file_lines(self.file_lines);
691 }
692
693 if self.unstable_features {
694 config.set_cli().unstable_features(self.unstable_features);
695 } else {
696 config.set().unstable_features(self.unstable_features);
697 }
698 if let Some(skip_children) = self.skip_children {
699 config.set_cli().skip_children(skip_children);
700 }
701 if let Some(error_on_unformatted) = self.error_on_unformatted {
702 config.set_cli().error_on_unformatted(error_on_unformatted);
703 }
704 if let Some(edition) = self.edition {
705 config.set_cli().edition(edition);
706 }
707 if let Some(edition) = self.style_edition {
708 config.set_cli().style_edition(edition);
709 }
710 if self.check {
711 config.set_cli().emit_mode(EmitMode::Diff);
712 } else if let Some(emit_mode) = self.emit_mode {
713 config.set_cli().emit_mode(emit_mode);
714 }
715 if self.backup {
716 config.set_cli().make_backup(true);
717 }
718 if let Some(color) = self.color {
719 config.set_cli().color(color);
720 }
721 if self.print_misformatted_file_names {
722 config.set_cli().print_misformatted_file_names(true);
723 }
724
725 for (key, val) in self.inline_config {
726 config.override_value(&key, &val);
727 }
728 }
729
730 fn config_path(&self) -> Option<&Path> {
731 self.config_path.as_deref()
732 }
733
734 fn edition(&self) -> Option<Edition> {
735 self.inline_config
736 .get("edition")
737 .map_or(self.edition, |e| Edition::from_str(e).ok())
738 }
739
740 fn style_edition(&self) -> Option<StyleEdition> {
741 self.inline_config
742 .get("style_edition")
743 .map_or(self.style_edition, |se| StyleEdition::from_str(se).ok())
744 }
745
746 fn version(&self) -> Option<Version> {
747 self.inline_config
748 .get("version")
749 .map(|version| Version::from_str(version).ok())
750 .flatten()
751 }
752}
753
754fn edition_from_edition_str(edition_str: &str) -> Result<Edition> {
755 match edition_str {
756 "2015" => Ok(Edition::Edition2015),
757 "2018" => Ok(Edition::Edition2018),
758 "2021" => Ok(Edition::Edition2021),
759 "2024" => Ok(Edition::Edition2024),
760 _ => Err(format_err!("Invalid value for `--edition`")),
761 }
762}
763
764fn style_edition_from_style_edition_str(edition_str: &str) -> Result<StyleEdition> {
765 match edition_str {
766 "2015" => Ok(StyleEdition::Edition2015),
767 "2018" => Ok(StyleEdition::Edition2018),
768 "2021" => Ok(StyleEdition::Edition2021),
769 "2024" => Ok(StyleEdition::Edition2024),
770 _ => Err(format_err!("Invalid value for `--style-edition`")),
771 }
772}
773
774fn emit_mode_from_emit_str(emit_str: &str) -> Result<EmitMode> {
775 match emit_str {
776 "files" => Ok(EmitMode::Files),
777 "stdout" => Ok(EmitMode::Stdout),
778 "coverage" => Ok(EmitMode::Coverage),
779 "checkstyle" => Ok(EmitMode::Checkstyle),
780 "json" => Ok(EmitMode::Json),
781 _ => Err(format_err!("Invalid value for `--emit`")),
782 }
783}
784
785#[cfg(test)]
786#[allow(dead_code)]
787mod test {
788 use super::*;
789 use rustfmt_config_proc_macro::nightly_only_test;
790
791 fn get_config<O: CliOptions>(path: Option<&Path>, options: Option<O>) -> Config {
792 load_config(path, options).unwrap().0
793 }
794
795 #[nightly_only_test]
796 #[test]
797 fn flag_sets_style_edition_override_correctly() {
798 let mut options = GetOptsOptions::default();
799 options.style_edition = Some(StyleEdition::Edition2024);
800 let config = get_config(None, Some(options));
801 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
802 }
803
804 #[nightly_only_test]
805 #[test]
806 fn edition_sets_style_edition_override_correctly() {
807 let mut options = GetOptsOptions::default();
808 options.edition = Some(Edition::Edition2024);
809 let config = get_config(None, Some(options));
810 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
811 }
812
813 #[nightly_only_test]
814 #[test]
815 fn version_sets_style_edition_override_correctly() {
816 let mut options = GetOptsOptions::default();
817 options.inline_config = HashMap::from([("version".to_owned(), "Two".to_owned())]);
818 let config = get_config(None, Some(options));
819 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
820 }
821
822 #[nightly_only_test]
823 #[test]
824 fn version_config_file_sets_style_edition_override_correctly() {
825 let options = GetOptsOptions::default();
826 let config_file = Some(Path::new("tests/config/style-edition/just-version"));
827 let config = get_config(config_file, Some(options));
828 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
829 }
830
831 #[nightly_only_test]
832 #[test]
833 fn style_edition_flag_has_correct_precedence_over_edition() {
834 let mut options = GetOptsOptions::default();
835 options.style_edition = Some(StyleEdition::Edition2021);
836 options.edition = Some(Edition::Edition2024);
837 let config = get_config(None, Some(options));
838 assert_eq!(config.style_edition(), StyleEdition::Edition2021);
839 }
840
841 #[nightly_only_test]
842 #[test]
843 fn style_edition_flag_has_correct_precedence_over_version() {
844 let mut options = GetOptsOptions::default();
845 options.style_edition = Some(StyleEdition::Edition2018);
846 options.inline_config = HashMap::from([("version".to_owned(), "Two".to_owned())]);
847 let config = get_config(None, Some(options));
848 assert_eq!(config.style_edition(), StyleEdition::Edition2018);
849 }
850
851 #[nightly_only_test]
852 #[test]
853 fn style_edition_flag_has_correct_precedence_over_edition_version() {
854 let mut options = GetOptsOptions::default();
855 options.style_edition = Some(StyleEdition::Edition2021);
856 options.edition = Some(Edition::Edition2018);
857 options.inline_config = HashMap::from([("version".to_owned(), "Two".to_owned())]);
858 let config = get_config(None, Some(options));
859 assert_eq!(config.style_edition(), StyleEdition::Edition2021);
860 }
861
862 #[nightly_only_test]
863 #[test]
864 fn style_edition_inline_has_correct_precedence_over_edition_version() {
865 let mut options = GetOptsOptions::default();
866 options.edition = Some(Edition::Edition2018);
867 options.inline_config = HashMap::from([
868 ("version".to_owned(), "One".to_owned()),
869 ("style_edition".to_owned(), "2024".to_owned()),
870 ]);
871 let config = get_config(None, Some(options));
872 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
873 }
874
875 #[nightly_only_test]
876 #[test]
877 fn style_edition_config_file_trumps_edition_flag_version_inline() {
878 let mut options = GetOptsOptions::default();
879 let config_file = Some(Path::new("tests/config/style-edition/just-style-edition"));
880 options.edition = Some(Edition::Edition2018);
881 options.inline_config = HashMap::from([("version".to_owned(), "One".to_owned())]);
882 let config = get_config(config_file, Some(options));
883 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
884 }
885
886 #[nightly_only_test]
887 #[test]
888 fn style_edition_config_file_trumps_edition_config_and_version_inline() {
889 let mut options = GetOptsOptions::default();
890 let config_file = Some(Path::new(
891 "tests/config/style-edition/style-edition-and-edition",
892 ));
893 options.inline_config = HashMap::from([("version".to_owned(), "Two".to_owned())]);
894 let config = get_config(config_file, Some(options));
895 assert_eq!(config.style_edition(), StyleEdition::Edition2021);
896 assert_eq!(config.edition(), Edition::Edition2024);
897 }
898
899 #[nightly_only_test]
900 #[test]
901 fn version_config_trumps_edition_config_and_flag() {
902 let mut options = GetOptsOptions::default();
903 let config_file = Some(Path::new("tests/config/style-edition/version-edition"));
904 options.edition = Some(Edition::Edition2018);
905 let config = get_config(config_file, Some(options));
906 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
907 }
908
909 #[nightly_only_test]
910 #[test]
911 fn style_edition_config_file_trumps_version_config() {
912 let options = GetOptsOptions::default();
913 let config_file = Some(Path::new(
914 "tests/config/style-edition/version-style-edition",
915 ));
916 let config = get_config(config_file, Some(options));
917 assert_eq!(config.style_edition(), StyleEdition::Edition2021);
918 }
919
920 #[nightly_only_test]
921 #[test]
922 fn style_edition_config_file_trumps_edition_version_config() {
923 let options = GetOptsOptions::default();
924 let config_file = Some(Path::new(
925 "tests/config/style-edition/version-style-edition-and-edition",
926 ));
927 let config = get_config(config_file, Some(options));
928 assert_eq!(config.style_edition(), StyleEdition::Edition2021);
929 }
930
931 #[nightly_only_test]
932 #[test]
933 fn correct_defaults_for_style_edition_loaded() {
934 let mut options = GetOptsOptions::default();
935 options.style_edition = Some(StyleEdition::Edition2024);
936 let config = get_config(None, Some(options));
937 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
938 }
939
940 #[nightly_only_test]
941 #[test]
942 fn style_edition_defaults_overridden_from_config() {
943 let options = GetOptsOptions::default();
944 let config_file = Some(Path::new("tests/config/style-edition/overrides"));
945 let config = get_config(config_file, Some(options));
946 assert_eq!(config.style_edition(), StyleEdition::Edition2024);
947 assert_eq!(config.overflow_delimited_expr(), false);
950 }
951
952 #[nightly_only_test]
953 #[test]
954 fn style_edition_defaults_overridden_from_cli() {
955 let mut options = GetOptsOptions::default();
956 let config_file = Some(Path::new("tests/config/style-edition/just-style-edition"));
957 options.inline_config =
958 HashMap::from([("overflow_delimited_expr".to_owned(), "false".to_owned())]);
959 let config = get_config(config_file, Some(options));
960 assert_eq!(config.overflow_delimited_expr(), false);
963 }
964}