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