1use std::cell::Cell;
2use std::fs::File;
3use std::io::{Error, ErrorKind, Read};
4use std::path::{Path, PathBuf};
5use std::{env, fs};
6
7use thiserror::Error;
8
9use crate::config::config_type::ConfigType;
10#[allow(unreachable_pub)]
11pub use crate::config::file_lines::{FileLines, FileName, Range};
12#[allow(unreachable_pub)]
13pub use crate::config::macro_names::MacroSelector;
14#[allow(unreachable_pub)]
15pub use crate::config::options::*;
16
17#[macro_use]
18pub(crate) mod config_type;
19#[macro_use]
20#[allow(unreachable_pub)]
21pub(crate) mod options;
22
23pub(crate) mod file_lines;
24#[allow(unreachable_pub)]
25pub(crate) mod lists;
26pub(crate) mod macro_names;
27pub(crate) mod style_edition;
28
29create_config! {
34 max_width: MaxWidth, true, "Maximum width of each line";
36 hard_tabs: HardTabs, true, "Use tab characters for indentation, spaces for alignment";
37 tab_spaces: TabSpaces, true, "Number of spaces per tab";
38 newline_style: NewlineStyleConfig, true, "Unix or Windows line endings";
39 indent_style: IndentStyleConfig, false, "How do we indent expressions or items";
40
41 use_small_heuristics: UseSmallHeuristics, true, "Whether to use different \
43 formatting for items and expressions if they satisfy a heuristic notion of 'small'";
44 width_heuristics: WidthHeuristicsConfig, false, "'small' heuristic values";
45 fn_call_width: FnCallWidth, true, "Maximum width of the args of a function call before \
46 falling back to vertical formatting.";
47 attr_fn_like_width: AttrFnLikeWidth, true, "Maximum width of the args of a function-like \
48 attributes before falling back to vertical formatting.";
49 struct_lit_width: StructLitWidth, true, "Maximum width in the body of a struct lit before \
50 falling back to vertical formatting.";
51 struct_variant_width: StructVariantWidth, true, "Maximum width in the body of a struct variant \
52 before falling back to vertical formatting.";
53 array_width: ArrayWidth, true, "Maximum width of an array literal before falling \
54 back to vertical formatting.";
55 chain_width: ChainWidth, true, "Maximum length of a chain to fit on a single line.";
56 single_line_if_else_max_width: SingleLineIfElseMaxWidth, true, "Maximum line length for single \
57 line if-else expressions. A value of zero means always break if-else expressions.";
58 single_line_let_else_max_width: SingleLineLetElseMaxWidth, true, "Maximum line length for \
59 single line let-else statements. A value of zero means always format the divergent `else` \
60 block over multiple lines.";
61
62 wrap_comments: WrapComments, false, "Break comments to fit on the line";
64 format_code_in_doc_comments: FormatCodeInDocComments, false, "Format the code snippet in \
65 doc comments.";
66 doc_comment_code_block_width: DocCommentCodeBlockWidth, false, "Maximum width for code \
67 snippets in doc comments. No effect unless format_code_in_doc_comments = true";
68 comment_width: CommentWidth, false,
69 "Maximum length of comments. No effect unless wrap_comments = true";
70 normalize_comments: NormalizeComments, false, "Convert /* */ comments to // comments where \
71 possible";
72 normalize_doc_attributes: NormalizeDocAttributes, false, "Normalize doc attributes as doc \
73 comments";
74 format_strings: FormatStrings, false, "Format string literals where necessary";
75 format_macro_matchers: FormatMacroMatchers, false,
76 "Format the metavariable matching patterns in macros";
77 format_macro_bodies: FormatMacroBodies, false,
78 "Format the bodies of declarative macro definitions";
79 skip_macro_invocations: SkipMacroInvocations, false,
80 "Skip formatting the bodies of macros invoked with the following names.";
81 hex_literal_case: HexLiteralCaseConfig, false, "Format hexadecimal integer literals";
82 float_literal_trailing_zero: FloatLiteralTrailingZeroConfig, false,
83 "Add or remove trailing zero in floating-point literals";
84
85 empty_item_single_line: EmptyItemSingleLine, false,
87 "Put empty-body functions and impls on a single line";
88 struct_lit_single_line: StructLitSingleLine, false,
89 "Put small struct literals on a single line";
90 fn_single_line: FnSingleLine, false, "Put single-expression functions on a single line";
91 where_single_line: WhereSingleLine, false, "Force where-clauses to be on a single line";
92
93 imports_indent: ImportsIndent, false, "Indent of imports";
95 imports_layout: ImportsLayout, false, "Item layout inside a import block";
96 imports_granularity: ImportsGranularityConfig, false,
97 "Merge or split imports to the provided granularity";
98 group_imports: GroupImportsTacticConfig, false,
99 "Controls the strategy for how imports are grouped together";
100 merge_imports: MergeImports, false, "(deprecated: use imports_granularity instead)";
101
102 reorder_imports: ReorderImports, true, "Reorder import and extern crate statements \
104 alphabetically";
105 reorder_modules: ReorderModules, true, "Reorder module statements alphabetically in group";
106 reorder_impl_items: ReorderImplItems, false, "Reorder impl items";
107
108 type_punctuation_density: TypePunctuationDensity, false,
110 "Determines if '+' or '=' are wrapped in spaces in the punctuation of types";
111 space_before_colon: SpaceBeforeColon, false, "Leave a space before the colon";
112 space_after_colon: SpaceAfterColon, false, "Leave a space after the colon";
113 spaces_around_ranges: SpacesAroundRanges, false, "Put spaces around the .. and ..= range \
114 operators";
115 binop_separator: BinopSeparator, false,
116 "Where to put a binary operator when a binary expression goes multiline";
117
118 remove_nested_parens: RemoveNestedParens, true, "Remove nested parens";
120 combine_control_expr: CombineControlExpr, false, "Combine control expressions with function \
121 calls";
122 short_array_element_width_threshold: ShortArrayElementWidthThreshold, true,
123 "Width threshold for an array element to be considered short";
124 overflow_delimited_expr: OverflowDelimitedExpr, false,
125 "Allow trailing bracket/brace delimited expressions to overflow";
126 struct_field_align_threshold: StructFieldAlignThreshold, false,
127 "Align struct fields if their diffs fits within threshold";
128 enum_discrim_align_threshold: EnumDiscrimAlignThreshold, false,
129 "Align enum variants discrims, if their diffs fit within threshold";
130 match_arm_blocks: MatchArmBlocks, false, "Wrap the body of arms in blocks when it does not fit \
131 on the same line with the pattern of arms";
132 match_arm_leading_pipes: MatchArmLeadingPipeConfig, true,
133 "Determines whether leading pipes are emitted on match arms";
134 match_arm_indent: MatchArmIndent, false,
135 "Determines whether match arms are indented";
136 force_multiline_blocks: ForceMultilineBlocks, false,
137 "Force multiline closure bodies and match arms to be wrapped in a block";
138 fn_args_layout: FnArgsLayout, true,
139 "(deprecated: use fn_params_layout instead)";
140 fn_params_layout: FnParamsLayout, true,
141 "Control the layout of parameters in function signatures.";
142 brace_style: BraceStyleConfig, false, "Brace style for items";
143 control_brace_style: ControlBraceStyleConfig, false,
144 "Brace style for control flow constructs";
145 trailing_semicolon: TrailingSemicolon, false,
146 "Add trailing semicolon after break, continue and return";
147 trailing_comma: TrailingComma, false,
148 "How to handle trailing commas for lists";
149 match_block_trailing_comma: MatchBlockTrailingComma, true,
150 "Put a trailing comma after a block based match arm (non-block arms are not affected)";
151 blank_lines_upper_bound: BlankLinesUpperBound, false,
152 "Maximum number of blank lines which can be put between items";
153 blank_lines_lower_bound: BlankLinesLowerBound, false,
154 "Minimum number of blank lines which must be put between items";
155 edition: EditionConfig, true, "The edition of the parser (RFC 2052)";
156 style_edition: StyleEditionConfig, true, "The edition of the Style Guide (RFC 3338)";
157 version: VersionConfig, false, "Version of formatting rules";
158 inline_attribute_width: InlineAttributeWidth, false,
159 "Write an item and its attribute on the same line \
160 if their combined width is below a threshold";
161 format_generated_files: FormatGeneratedFiles, false, "Format generated files";
162 generated_marker_line_search_limit: GeneratedMarkerLineSearchLimit, false, "Number of lines to \
163 check for a `@generated` marker when `format_generated_files` is enabled";
164
165 merge_derives: MergeDerives, true, "Merge multiple `#[derive(...)]` into a single one";
167 use_try_shorthand: UseTryShorthand, true, "Replace uses of the try! macro by the ? shorthand";
168 use_field_init_shorthand: UseFieldInitShorthand, true, "Use field initialization shorthand if \
169 possible";
170 force_explicit_abi: ForceExplicitAbi, true, "Always print the abi for extern items";
171 condense_wildcard_suffixes: CondenseWildcardSuffixes, false, "Replace strings of _ wildcards \
172 by a single .. in tuple patterns";
173
174 color: ColorConfig, false,
176 "What Color option to use when none is supplied: Always, Never, Auto";
177 required_version: RequiredVersion, false,
178 "Require a specific version of rustfmt";
179 unstable_features: UnstableFeatures, false,
180 "Enables unstable features. Only available on nightly channel";
181 disable_all_formatting: DisableAllFormatting, true, "Don't reformat anything";
182 skip_children: SkipChildren, false, "Don't reformat out of line modules";
183 hide_parse_errors: HideParseErrors, false, "Hide errors from the parser";
184 show_parse_errors: ShowParseErrors, false, "Show errors from the parser (unstable)";
185 error_on_line_overflow: ErrorOnLineOverflow, false, "Error if unable to get all lines within \
186 max_width";
187 error_on_unformatted: ErrorOnUnformatted, false,
188 "Error if unable to get comments or string literals within max_width, \
189 or they are left with trailing whitespaces";
190 ignore: Ignore, false,
191 "Skip formatting the specified files and directories";
192
193 verbose: Verbose, false, "How much to information to emit to the user";
195 file_lines: FileLinesConfig, false,
196 "Lines to format; this is not supported in rustfmt.toml, and can only be specified \
197 via the --file-lines option";
198 emit_mode: EmitModeConfig, false,
199 "What emit Mode to use when none is supplied";
200 make_backup: MakeBackup, false, "Backup changed files";
201 print_misformatted_file_names: PrintMisformattedFileNames, true,
202 "Prints the names of mismatched files that were formatted. Prints the names of \
203 files that would be formatted when used with `--check` mode. ";
204}
205
206#[derive(Error, Debug)]
207#[error("Could not output config: {0}")]
208pub struct ToTomlError(toml::ser::Error);
209
210impl PartialConfig {
211 pub fn to_toml(&self) -> Result<String, ToTomlError> {
212 let mut cloned = self.clone();
214 cloned.file_lines = None;
215 cloned.verbose = None;
216 cloned.width_heuristics = None;
217 cloned.print_misformatted_file_names = None;
218 cloned.merge_imports = None;
219 cloned.fn_args_layout = None;
220 cloned.hide_parse_errors = None;
221
222 ::toml::to_string(&cloned).map_err(ToTomlError)
223 }
224
225 pub(super) fn to_parsed_config(
226 self,
227 style_edition_override: Option<StyleEdition>,
228 edition_override: Option<Edition>,
229 version_override: Option<Version>,
230 dir: &Path,
231 ) -> Config {
232 Config::default_for_possible_style_edition(
233 style_edition_override.or(self.style_edition),
234 edition_override.or(self.edition),
235 version_override.or(self.version),
236 )
237 .fill_from_parsed_config(self, dir)
238 }
239}
240
241fn check_semver_version(range_requirement: &str, actual: &str) -> bool {
242 let mut version_req = match semver::VersionReq::parse(range_requirement) {
243 Ok(r) => r,
244 Err(e) => {
245 eprintln!("Error: failed to parse required version {range_requirement:?}: {e}");
246 return false;
247 }
248 };
249 let actual_version = match semver::Version::parse(actual) {
250 Ok(v) => v,
251 Err(e) => {
252 eprintln!("Error: failed to parse current version {actual:?}: {e}");
253 return false;
254 }
255 };
256
257 range_requirement
258 .split(',')
259 .enumerate()
260 .for_each(|(i, label)| {
261 let Some(comparator) = version_req.comparators.get_mut(i) else {
263 return;
264 };
265
266 if !label.starts_with('^') && comparator.op == semver::Op::Caret {
271 comparator.op = semver::Op::Exact;
272 }
273 });
274
275 version_req.matches(&actual_version)
276}
277
278impl Config {
279 pub fn default_for_possible_style_edition(
280 style_edition: Option<StyleEdition>,
281 edition: Option<Edition>,
282 version: Option<Version>,
283 ) -> Config {
284 match (style_edition, version, edition) {
291 (Some(se), _, _) => Self::default_with_style_edition(se),
292 (None, Some(Version::Two), _) => {
293 Self::default_with_style_edition(StyleEdition::Edition2024)
294 }
295 (None, Some(Version::One), _) => {
296 Self::default_with_style_edition(StyleEdition::Edition2015)
297 }
298 (None, None, Some(e)) => Self::default_with_style_edition(e.into()),
299 (None, None, None) => Config::default(),
300 }
301 }
302
303 pub(crate) fn version_meets_requirement(&self) -> bool {
304 if self.was_set().required_version() {
305 let version = env!("CARGO_PKG_VERSION");
306 let required_version = self.required_version();
307 if !check_semver_version(&required_version, version) {
308 eprintln!(
309 "Error: rustfmt version ({}) doesn't match the required version ({})",
310 version, required_version
311 );
312 return false;
313 }
314 }
315
316 true
317 }
318
319 pub(super) fn from_toml_path(
327 file_path: &Path,
328 edition: Option<Edition>,
329 style_edition: Option<StyleEdition>,
330 version: Option<Version>,
331 ) -> Result<Config, Error> {
332 let mut file = File::open(&file_path)?;
333 let mut toml = String::new();
334 file.read_to_string(&mut toml)?;
335 Config::from_toml_for_style_edition(&toml, file_path, edition, style_edition, version)
336 .map_err(|err| Error::new(ErrorKind::InvalidData, err))
337 }
338
339 pub(super) fn from_resolved_toml_path(
349 dir: &Path,
350 edition: Option<Edition>,
351 style_edition: Option<StyleEdition>,
352 version: Option<Version>,
353 ) -> Result<(Config, Option<PathBuf>), Error> {
354 fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
358 let mut current = if dir.is_relative() {
359 env::current_dir()?.join(dir)
360 } else {
361 dir.to_path_buf()
362 };
363
364 current = fs::canonicalize(current)?;
365
366 loop {
367 match get_toml_path(¤t) {
368 Ok(Some(path)) => return Ok(Some(path)),
369 Err(e) => return Err(e),
370 _ => (),
371 }
372
373 if !current.pop() {
375 break;
376 }
377 }
378
379 if let Some(home_dir) = dirs::home_dir() {
381 if let Some(path) = get_toml_path(&home_dir)? {
382 return Ok(Some(path));
383 }
384 }
385
386 if let Some(mut config_dir) = dirs::config_dir() {
388 config_dir.push("rustfmt");
389 if let Some(path) = get_toml_path(&config_dir)? {
390 return Ok(Some(path));
391 }
392 }
393
394 Ok(None)
395 }
396
397 match resolve_project_file(dir)? {
398 None => Ok((
399 Config::default_for_possible_style_edition(style_edition, edition, version),
400 None,
401 )),
402 Some(path) => Config::from_toml_path(&path, edition, style_edition, version)
403 .map(|config| (config, Some(path))),
404 }
405 }
406
407 #[allow(dead_code)]
408 pub(super) fn from_toml(toml: &str, file_path: &Path) -> Result<Config, String> {
409 Self::from_toml_for_style_edition(toml, file_path, None, None, None)
410 }
411
412 pub(crate) fn from_toml_for_style_edition(
413 toml: &str,
414 file_path: &Path,
415 edition: Option<Edition>,
416 style_edition: Option<StyleEdition>,
417 version: Option<Version>,
418 ) -> Result<Config, String> {
419 let parsed: ::toml::Value =
420 toml::from_str(toml).map_err(|e| format!("Could not parse TOML: {}", e))?;
421 let mut err = String::new();
422 let table = parsed
423 .as_table()
424 .ok_or_else(|| String::from("Parsed config was not table"))?;
425 for key in table.keys() {
426 if !Config::is_valid_name(key) {
427 let msg = &format!("Warning: Unknown configuration option `{key}`\n");
428 err.push_str(msg)
429 }
430 }
431
432 match parsed.try_into::<PartialConfig>() {
433 Ok(parsed_config) => {
434 if !err.is_empty() {
435 eprint!("{err}");
436 }
437 let dir = file_path.parent().ok_or_else(|| {
438 format!("failed to get parent directory for {}", file_path.display())
439 })?;
440
441 Ok(parsed_config.to_parsed_config(style_edition, edition, version, dir))
442 }
443 Err(e) => {
444 let err_msg = format!(
445 "The file `{}` failed to parse.\nError details: {e}",
446 file_path.display()
447 );
448 err.push_str(&err_msg);
449 Err(err_msg)
450 }
451 }
452 }
453}
454
455pub fn load_config<O: CliOptions>(
458 file_path: Option<&Path>,
459 options: Option<O>,
460) -> Result<(Config, Option<PathBuf>), Error> {
461 let (over_ride, edition, style_edition, version) = match options {
462 Some(ref opts) => (
463 config_path(opts)?,
464 opts.edition(),
465 opts.style_edition(),
466 opts.version(),
467 ),
468 None => (None, None, None, None),
469 };
470
471 let result = if let Some(over_ride) = over_ride {
472 Config::from_toml_path(over_ride.as_ref(), edition, style_edition, version)
473 .map(|p| (p, Some(over_ride.to_owned())))
474 } else if let Some(file_path) = file_path {
475 Config::from_resolved_toml_path(file_path, edition, style_edition, version)
476 } else {
477 Ok((
478 Config::default_for_possible_style_edition(style_edition, edition, version),
479 None,
480 ))
481 };
482
483 result.map(|(mut c, p)| {
484 if let Some(options) = options {
485 options.apply_to(&mut c);
486 }
487 (c, p)
488 })
489}
490
491fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
495 const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"];
496 for config_file_name in &CONFIG_FILE_NAMES {
497 let config_file = dir.join(config_file_name);
498 match fs::metadata(&config_file) {
499 Ok(ref md) if md.is_file() => return Ok(Some(config_file.canonicalize()?)),
502 Err(e) => {
507 if !matches!(e.kind(), ErrorKind::NotFound | ErrorKind::NotADirectory) {
508 let ctx = format!("Failed to get metadata for config file {:?}", &config_file);
509 let err = anyhow::Error::new(e).context(ctx);
510 return Err(Error::new(ErrorKind::Other, err));
511 }
512 }
513 _ => {}
514 }
515 }
516 Ok(None)
517}
518
519fn config_path(options: &dyn CliOptions) -> Result<Option<PathBuf>, Error> {
520 let config_path_not_found = |path: &str| -> Result<Option<PathBuf>, Error> {
521 Err(Error::new(
522 ErrorKind::NotFound,
523 format!(
524 "Error: unable to find a config file for the given path: `{}`",
525 path
526 ),
527 ))
528 };
529
530 match options.config_path() {
533 Some(path) if !path.exists() => config_path_not_found(path.to_str().unwrap()),
534 Some(path) if path.is_dir() => {
535 let config_file_path = get_toml_path(path)?;
536 if config_file_path.is_some() {
537 Ok(config_file_path)
538 } else {
539 config_path_not_found(path.to_str().unwrap())
540 }
541 }
542 Some(path) => Ok(Some(
543 path.canonicalize()?,
545 )),
546 None => Ok(None),
547 }
548}
549
550#[cfg(test)]
551mod test {
552 use super::*;
553 use std::str;
554
555 use crate::config::macro_names::{MacroName, MacroSelectors};
556 use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
557
558 #[allow(dead_code)]
559 mod mock {
560 use super::super::*;
561 use rustfmt_config_proc_macro::config_type;
562
563 #[config_type]
564 pub(crate) enum PartiallyUnstableOption {
565 V1,
566 V2,
567 #[unstable_variant]
568 V3,
569 }
570
571 config_option_with_style_edition_default!(
572 StableOption, bool, _ => false;
573 UnstableOption, bool, _ => false;
574 PartiallyUnstable, PartiallyUnstableOption, _ => PartiallyUnstableOption::V1;
575 );
576
577 create_config! {
578 max_width: MaxWidth, true, "Maximum width of each line";
580 required_version: RequiredVersion, false, "Require a specific version of rustfmt.";
581 ignore: Ignore, false, "Skip formatting the specified files and directories.";
582 verbose: Verbose, false, "How much to information to emit to the user";
583 file_lines: FileLinesConfig, false,
584 "Lines to format; this is not supported in rustfmt.toml, and can only be specified \
585 via the --file-lines option";
586
587 imports_granularity: ImportsGranularityConfig, false, "Merge imports";
589 merge_imports: MergeImports, false, "(deprecated: use imports_granularity instead)";
590
591 fn_args_layout: FnArgsLayout, true, "(deprecated: use fn_params_layout instead)";
593 fn_params_layout: FnParamsLayout, true,
594 "Control the layout of parameters in a function signatures.";
595
596 hide_parse_errors: HideParseErrors, false,
598 "(deprecated: use show_parse_errors instead)";
599 show_parse_errors: ShowParseErrors, false,
600 "Show errors from the parser (unstable)";
601
602
603 use_small_heuristics: UseSmallHeuristics, true,
605 "Whether to use different formatting for items and \
606 expressions if they satisfy a heuristic notion of 'small'.";
607 width_heuristics: WidthHeuristicsConfig, false, "'small' heuristic values";
608
609 fn_call_width: FnCallWidth, true, "Maximum width of the args of a function call before \
610 falling back to vertical formatting.";
611 attr_fn_like_width: AttrFnLikeWidth, true, "Maximum width of the args of a \
612 function-like attributes before falling back to vertical formatting.";
613 struct_lit_width: StructLitWidth, true, "Maximum width in the body of a struct lit \
614 before falling back to vertical formatting.";
615 struct_variant_width: StructVariantWidth, true, "Maximum width in the body of a struct \
616 variant before falling back to vertical formatting.";
617 array_width: ArrayWidth, true, "Maximum width of an array literal before falling \
618 back to vertical formatting.";
619 chain_width: ChainWidth, true, "Maximum length of a chain to fit on a single line.";
620 single_line_if_else_max_width: SingleLineIfElseMaxWidth, true, "Maximum line length \
621 for single line if-else expressions. A value of zero means always break if-else \
622 expressions.";
623 single_line_let_else_max_width: SingleLineLetElseMaxWidth, false, "Maximum line length \
624 for single line let-else statements. A value of zero means always format the \
625 divergent `else` block over multiple lines.";
626
627 stable_option: StableOption, true, "A stable option";
629 unstable_option: UnstableOption, false, "An unstable option";
630 partially_unstable_option: PartiallyUnstable, true, "A partially unstable option";
631 edition: EditionConfig, true, "blah";
632 style_edition: StyleEditionConfig, true, "blah";
633 version: VersionConfig, false, "blah blah"
634 }
635
636 #[cfg(test)]
637 mod partially_unstable_option {
638 use super::{Config, PartialConfig, PartiallyUnstableOption};
639 use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
640 use std::path::Path;
641
642 #[test]
644 fn test_from_toml_stable_value() {
645 let toml = r#"
646 partially_unstable_option = "V2"
647 "#;
648 let partial_config: PartialConfig = toml::from_str(toml).unwrap();
649 let config = Config::default();
650 let config = config.fill_from_parsed_config(partial_config, Path::new(""));
651 assert_eq!(
652 config.partially_unstable_option(),
653 PartiallyUnstableOption::V2
654 );
655 }
656
657 #[stable_only_test]
659 #[test]
660 fn test_from_toml_unstable_value_on_stable() {
661 let toml = r#"
662 partially_unstable_option = "V3"
663 "#;
664 let partial_config: PartialConfig = toml::from_str(toml).unwrap();
665 let config = Config::default();
666 let config = config.fill_from_parsed_config(partial_config, Path::new(""));
667 assert_eq!(
668 config.partially_unstable_option(),
669 PartiallyUnstableOption::V1
671 );
672 }
673
674 #[nightly_only_test]
676 #[test]
677 fn test_from_toml_unstable_value_on_nightly() {
678 let toml = r#"
679 partially_unstable_option = "V3"
680 "#;
681 let partial_config: PartialConfig = toml::from_str(toml).unwrap();
682 let config = Config::default();
683 let config = config.fill_from_parsed_config(partial_config, Path::new(""));
684 assert_eq!(
685 config.partially_unstable_option(),
686 PartiallyUnstableOption::V3
687 );
688 }
689 }
690 }
691
692 #[test]
693 fn test_config_set() {
694 let mut config = Config::default();
695 config.set().verbose(Verbosity::Quiet);
696 assert_eq!(config.verbose(), Verbosity::Quiet);
697 config.set().verbose(Verbosity::Normal);
698 assert_eq!(config.verbose(), Verbosity::Normal);
699 }
700
701 #[test]
702 fn test_config_used_to_toml() {
703 let config = Config::default();
704
705 let merge_derives = config.merge_derives();
706 let skip_children = config.skip_children();
707
708 let used_options = config.used_options();
709 let toml = used_options.to_toml().unwrap();
710 assert_eq!(
711 toml,
712 format!("merge_derives = {merge_derives}\nskip_children = {skip_children}\n",)
713 );
714 }
715
716 #[test]
717 fn test_was_set() {
718 let config = Config::from_toml("hard_tabs = true", Path::new("./rustfmt.toml")).unwrap();
719
720 assert_eq!(config.was_set().hard_tabs(), true);
721 assert_eq!(config.was_set().verbose(), false);
722 }
723
724 const PRINT_DOCS_STABLE_OPTION: &str = "stable_option <boolean> Default: false";
725 const PRINT_DOCS_UNSTABLE_OPTION: &str = "unstable_option <boolean> Default: false (unstable)";
726 const PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION: &str =
727 "partially_unstable_option [V1|V2|V3 (unstable)] Default: V1";
728
729 #[test]
730 fn test_print_docs_exclude_unstable() {
731 use self::mock::Config;
732
733 let mut output = Vec::new();
734 Config::print_docs(&mut output, false);
735
736 let s = str::from_utf8(&output).unwrap();
737 assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true);
738 assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), false);
739 assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
740 }
741
742 #[test]
743 fn test_print_docs_include_unstable() {
744 use self::mock::Config;
745
746 let mut output = Vec::new();
747 Config::print_docs(&mut output, true);
748
749 let s = str::from_utf8(&output).unwrap();
750 assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true);
751 assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), true);
752 assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
753 }
754
755 #[test]
756 fn test_dump_default_config() {
757 let default_config = format!(
758 r#"max_width = 100
759hard_tabs = false
760tab_spaces = 4
761newline_style = "Auto"
762indent_style = "Block"
763use_small_heuristics = "Default"
764fn_call_width = 60
765attr_fn_like_width = 70
766struct_lit_width = 18
767struct_variant_width = 35
768array_width = 60
769chain_width = 60
770single_line_if_else_max_width = 50
771single_line_let_else_max_width = 50
772wrap_comments = false
773format_code_in_doc_comments = false
774doc_comment_code_block_width = 100
775comment_width = 80
776normalize_comments = false
777normalize_doc_attributes = false
778format_strings = false
779format_macro_matchers = false
780format_macro_bodies = true
781skip_macro_invocations = []
782hex_literal_case = "Preserve"
783float_literal_trailing_zero = "Preserve"
784empty_item_single_line = true
785struct_lit_single_line = true
786fn_single_line = false
787where_single_line = false
788imports_indent = "Block"
789imports_layout = "Mixed"
790imports_granularity = "Preserve"
791group_imports = "Preserve"
792reorder_imports = true
793reorder_modules = true
794reorder_impl_items = false
795type_punctuation_density = "Wide"
796space_before_colon = false
797space_after_colon = true
798spaces_around_ranges = false
799binop_separator = "Front"
800remove_nested_parens = true
801combine_control_expr = true
802short_array_element_width_threshold = 10
803overflow_delimited_expr = false
804struct_field_align_threshold = 0
805enum_discrim_align_threshold = 0
806match_arm_blocks = true
807match_arm_leading_pipes = "Never"
808match_arm_indent = true
809force_multiline_blocks = false
810fn_params_layout = "Tall"
811brace_style = "SameLineWhere"
812control_brace_style = "AlwaysSameLine"
813trailing_semicolon = true
814trailing_comma = "Vertical"
815match_block_trailing_comma = false
816blank_lines_upper_bound = 1
817blank_lines_lower_bound = 0
818edition = "2015"
819style_edition = "2015"
820version = "One"
821inline_attribute_width = 0
822format_generated_files = true
823generated_marker_line_search_limit = 5
824merge_derives = true
825use_try_shorthand = false
826use_field_init_shorthand = false
827force_explicit_abi = true
828condense_wildcard_suffixes = false
829color = "Auto"
830required_version = "{}"
831unstable_features = false
832disable_all_formatting = false
833skip_children = false
834show_parse_errors = true
835error_on_line_overflow = false
836error_on_unformatted = false
837ignore = []
838emit_mode = "Files"
839make_backup = false
840"#,
841 env!("CARGO_PKG_VERSION")
842 );
843 let toml = Config::default().all_options().to_toml().unwrap();
844 assert_eq!(&toml, &default_config);
845 }
846
847 #[test]
848 fn test_dump_style_edition_2024_config() {
849 let edition_2024_config = format!(
850 r#"max_width = 100
851hard_tabs = false
852tab_spaces = 4
853newline_style = "Auto"
854indent_style = "Block"
855use_small_heuristics = "Default"
856fn_call_width = 60
857attr_fn_like_width = 70
858struct_lit_width = 18
859struct_variant_width = 35
860array_width = 60
861chain_width = 60
862single_line_if_else_max_width = 50
863single_line_let_else_max_width = 50
864wrap_comments = false
865format_code_in_doc_comments = false
866doc_comment_code_block_width = 100
867comment_width = 80
868normalize_comments = false
869normalize_doc_attributes = false
870format_strings = false
871format_macro_matchers = false
872format_macro_bodies = true
873skip_macro_invocations = []
874hex_literal_case = "Preserve"
875float_literal_trailing_zero = "Preserve"
876empty_item_single_line = true
877struct_lit_single_line = true
878fn_single_line = false
879where_single_line = false
880imports_indent = "Block"
881imports_layout = "Mixed"
882imports_granularity = "Preserve"
883group_imports = "Preserve"
884reorder_imports = true
885reorder_modules = true
886reorder_impl_items = false
887type_punctuation_density = "Wide"
888space_before_colon = false
889space_after_colon = true
890spaces_around_ranges = false
891binop_separator = "Front"
892remove_nested_parens = true
893combine_control_expr = true
894short_array_element_width_threshold = 10
895overflow_delimited_expr = false
896struct_field_align_threshold = 0
897enum_discrim_align_threshold = 0
898match_arm_blocks = true
899match_arm_leading_pipes = "Never"
900match_arm_indent = true
901force_multiline_blocks = false
902fn_params_layout = "Tall"
903brace_style = "SameLineWhere"
904control_brace_style = "AlwaysSameLine"
905trailing_semicolon = true
906trailing_comma = "Vertical"
907match_block_trailing_comma = false
908blank_lines_upper_bound = 1
909blank_lines_lower_bound = 0
910edition = "2015"
911style_edition = "2024"
912version = "Two"
913inline_attribute_width = 0
914format_generated_files = true
915generated_marker_line_search_limit = 5
916merge_derives = true
917use_try_shorthand = false
918use_field_init_shorthand = false
919force_explicit_abi = true
920condense_wildcard_suffixes = false
921color = "Auto"
922required_version = "{}"
923unstable_features = false
924disable_all_formatting = false
925skip_children = false
926show_parse_errors = true
927error_on_line_overflow = false
928error_on_unformatted = false
929ignore = []
930emit_mode = "Files"
931make_backup = false
932"#,
933 env!("CARGO_PKG_VERSION")
934 );
935 let toml = Config::default_with_style_edition(StyleEdition::Edition2024)
936 .all_options()
937 .to_toml()
938 .unwrap();
939 assert_eq!(&toml, &edition_2024_config);
940 }
941
942 #[test]
943 fn test_editions_2015_2018_2021_identical() {
944 let get_edition_toml = |style_edition: StyleEdition| {
945 Config::default_with_style_edition(style_edition)
946 .all_options()
947 .to_toml()
948 .unwrap()
949 };
950 let edition2015 = get_edition_toml(StyleEdition::Edition2015);
951 let edition2018 = get_edition_toml(StyleEdition::Edition2018);
952 let edition2021 = get_edition_toml(StyleEdition::Edition2021);
953 assert_eq!(edition2015, edition2018);
954 assert_eq!(edition2018, edition2021);
955 }
956
957 #[stable_only_test]
958 #[test]
959 fn test_as_not_nightly_channel() {
960 let mut config = Config::default();
961 assert_eq!(config.was_set().unstable_features(), false);
962 config.set().unstable_features(true);
963 assert_eq!(config.was_set().unstable_features(), false);
964 }
965
966 #[nightly_only_test]
967 #[test]
968 fn test_as_nightly_channel() {
969 let mut config = Config::default();
970 config.set().unstable_features(true);
971 assert_eq!(config.was_set().unstable_features(), false);
974 config.set().unstable_features(true);
975 assert_eq!(config.unstable_features(), true);
976 }
977
978 #[nightly_only_test]
979 #[test]
980 fn test_unstable_from_toml() {
981 let config =
982 Config::from_toml("unstable_features = true", Path::new("./rustfmt.toml")).unwrap();
983 assert_eq!(config.was_set().unstable_features(), true);
984 assert_eq!(config.unstable_features(), true);
985 }
986
987 #[test]
988 fn test_set_cli() {
989 let mut config = Config::default();
990 assert_eq!(config.was_set().edition(), false);
991 assert_eq!(config.was_set_cli().edition(), false);
992 config.set().edition(Edition::Edition2021);
993 assert_eq!(config.was_set().edition(), false);
994 assert_eq!(config.was_set_cli().edition(), false);
995 config.set_cli().edition(Edition::Edition2021);
996 assert_eq!(config.was_set().edition(), false);
997 assert_eq!(config.was_set_cli().edition(), true);
998 assert_eq!(config.was_set_cli().emit_mode(), false);
999 }
1000
1001 #[cfg(test)]
1002 mod deprecated_option_merge_imports {
1003 use super::*;
1004
1005 #[nightly_only_test]
1006 #[test]
1007 fn test_old_option_set() {
1008 let toml = r#"
1009 unstable_features = true
1010 merge_imports = true
1011 "#;
1012 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1013 assert_eq!(config.imports_granularity(), ImportGranularity::Crate);
1014 }
1015
1016 #[nightly_only_test]
1017 #[test]
1018 fn test_both_set() {
1019 let toml = r#"
1020 unstable_features = true
1021 merge_imports = true
1022 imports_granularity = "Preserve"
1023 "#;
1024 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1025 assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
1026 }
1027
1028 #[nightly_only_test]
1029 #[test]
1030 fn test_new_overridden() {
1031 let toml = r#"
1032 unstable_features = true
1033 merge_imports = true
1034 "#;
1035 let mut config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1036 config.override_value("imports_granularity", "Preserve");
1037 assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
1038 }
1039
1040 #[nightly_only_test]
1041 #[test]
1042 fn test_old_overridden() {
1043 let toml = r#"
1044 unstable_features = true
1045 imports_granularity = "Module"
1046 "#;
1047 let mut config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1048 config.override_value("merge_imports", "true");
1049 assert_eq!(config.imports_granularity(), ImportGranularity::Module);
1051 }
1052 }
1053
1054 #[cfg(test)]
1055 mod use_small_heuristics {
1056 use super::*;
1057
1058 #[test]
1059 fn test_default_sets_correct_widths() {
1060 let toml = r#"
1061 use_small_heuristics = "Default"
1062 max_width = 200
1063 "#;
1064 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1065 assert_eq!(config.array_width(), 120);
1066 assert_eq!(config.attr_fn_like_width(), 140);
1067 assert_eq!(config.chain_width(), 120);
1068 assert_eq!(config.fn_call_width(), 120);
1069 assert_eq!(config.single_line_if_else_max_width(), 100);
1070 assert_eq!(config.struct_lit_width(), 36);
1071 assert_eq!(config.struct_variant_width(), 70);
1072 }
1073
1074 #[test]
1075 fn test_max_sets_correct_widths() {
1076 let toml = r#"
1077 use_small_heuristics = "Max"
1078 max_width = 120
1079 "#;
1080 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1081 assert_eq!(config.array_width(), 120);
1082 assert_eq!(config.attr_fn_like_width(), 120);
1083 assert_eq!(config.chain_width(), 120);
1084 assert_eq!(config.fn_call_width(), 120);
1085 assert_eq!(config.single_line_if_else_max_width(), 120);
1086 assert_eq!(config.struct_lit_width(), 120);
1087 assert_eq!(config.struct_variant_width(), 120);
1088 }
1089
1090 #[test]
1091 fn test_off_sets_correct_widths() {
1092 let toml = r#"
1093 use_small_heuristics = "Off"
1094 max_width = 100
1095 "#;
1096 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1097 assert_eq!(config.array_width(), usize::MAX);
1098 assert_eq!(config.attr_fn_like_width(), usize::MAX);
1099 assert_eq!(config.chain_width(), usize::MAX);
1100 assert_eq!(config.fn_call_width(), usize::MAX);
1101 assert_eq!(config.single_line_if_else_max_width(), 0);
1102 assert_eq!(config.struct_lit_width(), 0);
1103 assert_eq!(config.struct_variant_width(), 0);
1104 }
1105
1106 #[test]
1107 fn test_override_works_with_default() {
1108 let toml = r#"
1109 use_small_heuristics = "Default"
1110 array_width = 20
1111 attr_fn_like_width = 40
1112 chain_width = 20
1113 fn_call_width = 90
1114 single_line_if_else_max_width = 40
1115 struct_lit_width = 30
1116 struct_variant_width = 34
1117 "#;
1118 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1119 assert_eq!(config.array_width(), 20);
1120 assert_eq!(config.attr_fn_like_width(), 40);
1121 assert_eq!(config.chain_width(), 20);
1122 assert_eq!(config.fn_call_width(), 90);
1123 assert_eq!(config.single_line_if_else_max_width(), 40);
1124 assert_eq!(config.struct_lit_width(), 30);
1125 assert_eq!(config.struct_variant_width(), 34);
1126 }
1127
1128 #[test]
1129 fn test_override_with_max() {
1130 let toml = r#"
1131 use_small_heuristics = "Max"
1132 array_width = 20
1133 attr_fn_like_width = 40
1134 chain_width = 20
1135 fn_call_width = 90
1136 single_line_if_else_max_width = 40
1137 struct_lit_width = 30
1138 struct_variant_width = 34
1139 "#;
1140 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1141 assert_eq!(config.array_width(), 20);
1142 assert_eq!(config.attr_fn_like_width(), 40);
1143 assert_eq!(config.chain_width(), 20);
1144 assert_eq!(config.fn_call_width(), 90);
1145 assert_eq!(config.single_line_if_else_max_width(), 40);
1146 assert_eq!(config.struct_lit_width(), 30);
1147 assert_eq!(config.struct_variant_width(), 34);
1148 }
1149
1150 #[test]
1151 fn test_override_with_off() {
1152 let toml = r#"
1153 use_small_heuristics = "Off"
1154 array_width = 20
1155 attr_fn_like_width = 40
1156 chain_width = 20
1157 fn_call_width = 90
1158 single_line_if_else_max_width = 40
1159 struct_lit_width = 30
1160 struct_variant_width = 34
1161 "#;
1162 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1163 assert_eq!(config.array_width(), 20);
1164 assert_eq!(config.attr_fn_like_width(), 40);
1165 assert_eq!(config.chain_width(), 20);
1166 assert_eq!(config.fn_call_width(), 90);
1167 assert_eq!(config.single_line_if_else_max_width(), 40);
1168 assert_eq!(config.struct_lit_width(), 30);
1169 assert_eq!(config.struct_variant_width(), 34);
1170 }
1171
1172 #[test]
1173 fn test_fn_call_width_config_exceeds_max_width() {
1174 let toml = r#"
1175 max_width = 90
1176 fn_call_width = 95
1177 "#;
1178 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1179 assert_eq!(config.fn_call_width(), 90);
1180 }
1181
1182 #[test]
1183 fn test_attr_fn_like_width_config_exceeds_max_width() {
1184 let toml = r#"
1185 max_width = 80
1186 attr_fn_like_width = 90
1187 "#;
1188 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1189 assert_eq!(config.attr_fn_like_width(), 80);
1190 }
1191
1192 #[test]
1193 fn test_struct_lit_config_exceeds_max_width() {
1194 let toml = r#"
1195 max_width = 78
1196 struct_lit_width = 90
1197 "#;
1198 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1199 assert_eq!(config.struct_lit_width(), 78);
1200 }
1201
1202 #[test]
1203 fn test_struct_variant_width_config_exceeds_max_width() {
1204 let toml = r#"
1205 max_width = 80
1206 struct_variant_width = 90
1207 "#;
1208 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1209 assert_eq!(config.struct_variant_width(), 80);
1210 }
1211
1212 #[test]
1213 fn test_array_width_config_exceeds_max_width() {
1214 let toml = r#"
1215 max_width = 60
1216 array_width = 80
1217 "#;
1218 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1219 assert_eq!(config.array_width(), 60);
1220 }
1221
1222 #[test]
1223 fn test_chain_width_config_exceeds_max_width() {
1224 let toml = r#"
1225 max_width = 80
1226 chain_width = 90
1227 "#;
1228 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1229 assert_eq!(config.chain_width(), 80);
1230 }
1231
1232 #[test]
1233 fn test_single_line_if_else_max_width_config_exceeds_max_width() {
1234 let toml = r#"
1235 max_width = 70
1236 single_line_if_else_max_width = 90
1237 "#;
1238 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1239 assert_eq!(config.single_line_if_else_max_width(), 70);
1240 }
1241
1242 #[test]
1243 fn test_override_fn_call_width_exceeds_max_width() {
1244 let mut config = Config::default();
1245 config.override_value("fn_call_width", "101");
1246 assert_eq!(config.fn_call_width(), 100);
1247 }
1248
1249 #[test]
1250 fn test_override_attr_fn_like_width_exceeds_max_width() {
1251 let mut config = Config::default();
1252 config.override_value("attr_fn_like_width", "101");
1253 assert_eq!(config.attr_fn_like_width(), 100);
1254 }
1255
1256 #[test]
1257 fn test_override_struct_lit_exceeds_max_width() {
1258 let mut config = Config::default();
1259 config.override_value("struct_lit_width", "101");
1260 assert_eq!(config.struct_lit_width(), 100);
1261 }
1262
1263 #[test]
1264 fn test_override_struct_variant_width_exceeds_max_width() {
1265 let mut config = Config::default();
1266 config.override_value("struct_variant_width", "101");
1267 assert_eq!(config.struct_variant_width(), 100);
1268 }
1269
1270 #[test]
1271 fn test_override_array_width_exceeds_max_width() {
1272 let mut config = Config::default();
1273 config.override_value("array_width", "101");
1274 assert_eq!(config.array_width(), 100);
1275 }
1276
1277 #[test]
1278 fn test_override_chain_width_exceeds_max_width() {
1279 let mut config = Config::default();
1280 config.override_value("chain_width", "101");
1281 assert_eq!(config.chain_width(), 100);
1282 }
1283
1284 #[test]
1285 fn test_override_single_line_if_else_max_width_exceeds_max_width() {
1286 let mut config = Config::default();
1287 config.override_value("single_line_if_else_max_width", "101");
1288 assert_eq!(config.single_line_if_else_max_width(), 100);
1289 }
1290 }
1291
1292 #[cfg(test)]
1293 mod partially_unstable_option {
1294 use super::mock::{Config, PartiallyUnstableOption};
1295
1296 #[test]
1298 fn test_override_stable_value() {
1299 let mut config = Config::default();
1300 config.override_value("partially_unstable_option", "V2");
1301 assert_eq!(
1302 config.partially_unstable_option(),
1303 PartiallyUnstableOption::V2
1304 );
1305 }
1306
1307 #[test]
1309 fn test_override_unstable_value() {
1310 let mut config = Config::default();
1311 config.override_value("partially_unstable_option", "V3");
1312 assert_eq!(
1313 config.partially_unstable_option(),
1314 PartiallyUnstableOption::V3
1315 );
1316 }
1317 }
1318
1319 #[test]
1320 fn test_override_skip_macro_invocations() {
1321 let mut config = Config::default();
1322 config.override_value("skip_macro_invocations", r#"["*", "println"]"#);
1323 assert_eq!(
1324 config.skip_macro_invocations(),
1325 MacroSelectors(vec![
1326 MacroSelector::All,
1327 MacroSelector::Name(MacroName::new("println".to_owned()))
1328 ])
1329 );
1330 }
1331
1332 #[cfg(test)]
1333 mod required_version {
1334 use super::*;
1335
1336 #[allow(dead_code)] fn get_current_version() -> semver::Version {
1338 semver::Version::parse(env!("CARGO_PKG_VERSION")).unwrap()
1339 }
1340
1341 #[nightly_only_test]
1342 #[test]
1343 fn test_required_version_default() {
1344 let config = Config::default();
1345 assert!(config.version_meets_requirement());
1346 }
1347
1348 #[nightly_only_test]
1349 #[test]
1350 fn test_current_required_version() {
1351 let toml = format!("required_version=\"{}\"", env!("CARGO_PKG_VERSION"));
1352 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1353
1354 assert!(config.version_meets_requirement());
1355 }
1356
1357 #[nightly_only_test]
1358 #[test]
1359 fn test_required_version_above() {
1360 let toml = "required_version=\"1000.0.0\"";
1361 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1362
1363 assert!(!config.version_meets_requirement());
1364 }
1365
1366 #[nightly_only_test]
1367 #[test]
1368 fn test_required_version_below() {
1369 let versions = vec!["0.0.0", "0.0.1", "0.1.0"];
1370
1371 for version in versions {
1372 let toml = format!("required_version=\"{}\"", version.to_string());
1373 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1374
1375 assert!(!config.version_meets_requirement());
1376 }
1377 }
1378
1379 #[nightly_only_test]
1380 #[test]
1381 fn test_required_version_tilde() {
1382 let toml = format!("required_version=\"~{}\"", env!("CARGO_PKG_VERSION"));
1383 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1384
1385 assert!(config.version_meets_requirement());
1386 }
1387
1388 #[nightly_only_test]
1389 #[test]
1390 fn test_required_version_caret() {
1391 let current_version = get_current_version();
1392
1393 for minor in current_version.minor..0 {
1394 let toml = format!(
1395 "required_version=\"^{}.{}.0\"",
1396 current_version.major.to_string(),
1397 minor.to_string()
1398 );
1399 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1400
1401 assert!(!config.version_meets_requirement());
1402 }
1403 }
1404
1405 #[nightly_only_test]
1406 #[test]
1407 fn test_required_version_greater_than() {
1408 let toml = "required_version=\">1.0.0\"";
1409 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1410
1411 assert!(config.version_meets_requirement());
1412 }
1413
1414 #[nightly_only_test]
1415 #[test]
1416 fn test_required_version_less_than() {
1417 let toml = "required_version=\"<1.0.0\"";
1418 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1419
1420 assert!(!config.version_meets_requirement());
1421 }
1422
1423 #[nightly_only_test]
1424 #[test]
1425 fn test_required_version_range() {
1426 let current_version = get_current_version();
1427
1428 let toml = format!(
1429 "required_version=\">={}.0.0, <{}.0.0\"",
1430 current_version.major,
1431 current_version.major + 1
1432 );
1433 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1434
1435 assert!(config.version_meets_requirement());
1436 }
1437
1438 #[nightly_only_test]
1439 #[test]
1440 fn test_required_version_exact_boundary() {
1441 let toml = format!("required_version=\"{}\"", get_current_version().to_string());
1442 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1443
1444 assert!(config.version_meets_requirement());
1445 }
1446
1447 #[nightly_only_test]
1448 #[test]
1449 fn test_required_version_pre_release() {
1450 let toml = format!(
1451 "required_version=\"^{}-alpha\"",
1452 get_current_version().to_string()
1453 );
1454 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1455
1456 assert!(config.version_meets_requirement());
1457 }
1458
1459 #[nightly_only_test]
1460 #[test]
1461 fn test_required_version_with_build_metadata() {
1462 let toml = format!(
1463 "required_version=\"{}+build.1\"",
1464 get_current_version().to_string()
1465 );
1466
1467 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1468
1469 assert!(config.version_meets_requirement());
1470 }
1471
1472 #[nightly_only_test]
1473 #[test]
1474 fn test_required_version_invalid_specification() {
1475 let toml = "required_version=\"not.a.version\"";
1476 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1477
1478 assert!(!config.version_meets_requirement())
1479 }
1480
1481 #[nightly_only_test]
1482 #[test]
1483 fn test_required_version_complex_range() {
1484 let current_version = get_current_version();
1485
1486 let toml = format!(
1487 "required_version=\">={}.0.0, <{}.0.0, ~{}.{}.0\"",
1488 current_version.major,
1489 current_version.major + 1,
1490 current_version.major,
1491 current_version.minor
1492 );
1493 let config = Config::from_toml(&toml, Path::new("./rustfmt.toml")).unwrap();
1494
1495 assert!(config.version_meets_requirement());
1496 }
1497
1498 #[nightly_only_test]
1499 #[test]
1500 fn test_required_version_wildcard_major() {
1501 let toml = "required_version=\"1.x\"";
1502 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1503
1504 assert!(config.version_meets_requirement());
1505 }
1506
1507 #[nightly_only_test]
1508 #[test]
1509 fn test_required_version_wildcard_any() {
1510 let toml = "required_version=\"*\"";
1511 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1512
1513 assert!(config.version_meets_requirement());
1514 }
1515
1516 #[nightly_only_test]
1517 #[test]
1518 fn test_required_version_major_version_zero() {
1519 let toml = "required_version=\"0.1.0\"";
1520 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1521
1522 assert!(!config.version_meets_requirement());
1523 }
1524
1525 #[nightly_only_test]
1526 #[test]
1527 fn test_required_version_future_major_version() {
1528 let toml = "required_version=\"3.0.0\"";
1529 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1530
1531 assert!(!config.version_meets_requirement());
1532 }
1533
1534 #[nightly_only_test]
1535 #[test]
1536 fn test_required_version_fail_different_operator() {
1537 let toml = "required_version=\"!=1.0.0\"";
1539 let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1540
1541 assert!(!config.version_meets_requirement());
1542 }
1543 }
1544
1545 #[cfg(test)]
1546 mod check_semver_version {
1547 use super::*;
1548
1549 #[test]
1550 fn test_exact_version_match() {
1551 assert!(check_semver_version("1.0.0", "1.0.0"));
1552 assert!(!check_semver_version("1.0.0", "1.1.0"));
1553 assert!(!check_semver_version("1.0.0", "1.0.1"));
1554 assert!(!check_semver_version("1.0.0", "2.1.0"));
1555 assert!(!check_semver_version("1.0.0", "0.1.0"));
1556 assert!(!check_semver_version("1.0.0", "0.0.1"));
1557 }
1558
1559 #[test]
1560 fn test_version_mismatch() {
1561 assert!(!check_semver_version("2.0.0", "1.0.0"));
1562 }
1563
1564 #[test]
1565 fn test_patch_version_greater() {
1566 assert!(check_semver_version("^1.0.0", "1.0.1"));
1567 }
1568
1569 #[test]
1570 fn test_minor_version_greater() {
1571 assert!(check_semver_version("^1.0.0", "1.1.0"));
1572 }
1573
1574 #[test]
1575 fn test_major_version_less() {
1576 assert!(!check_semver_version("1.0.0", "0.9.0"));
1577 }
1578
1579 #[test]
1580 fn test_prerelease_less_than_release() {
1581 assert!(!check_semver_version("1.0.0", "1.0.0-alpha"));
1582 }
1583
1584 #[test]
1585 fn test_prerelease_version_specific_match() {
1586 assert!(check_semver_version("1.0.0-alpha", "1.0.0-alpha"));
1587 }
1588
1589 #[test]
1590 fn test_build_metadata_ignored() {
1591 assert!(check_semver_version("1.0.0", "1.0.0+build.1"));
1592 }
1593
1594 #[test]
1595 fn test_greater_than_requirement() {
1596 assert!(check_semver_version(">1.0.0", "1.1.0"));
1597 }
1598
1599 #[test]
1600 fn test_less_than_requirement_fails_when_greater() {
1601 assert!(!check_semver_version("<1.0.0", "1.1.0"));
1602 }
1603
1604 #[test]
1605 fn test_caret_requirement_matches_minor_update() {
1606 assert!(check_semver_version("^1.1.0", "1.2.0"));
1607 }
1608
1609 #[test]
1610 fn test_tilde_requirement_matches_patch_update() {
1611 assert!(check_semver_version("~1.0.0", "1.0.1"));
1612 }
1613
1614 #[test]
1615 fn test_range_requirement_inclusive() {
1616 assert!(check_semver_version(">=1.0.0, <2.0.0", "1.5.0"));
1617 }
1618
1619 #[test]
1620 fn test_pre_release_specific_match() {
1621 assert!(check_semver_version("1.0.0-alpha.1", "1.0.0-alpha.1"));
1622 }
1623
1624 #[test]
1625 fn test_pre_release_non_match_when_requiring_release() {
1626 assert!(!check_semver_version("1.0.0", "1.0.0-alpha.1"));
1627 }
1628
1629 #[test]
1632 fn test_invalid_or() {
1633 assert!(!check_semver_version("1.0.0 || 2.0.0", "1.0.0"));
1634 assert!(!check_semver_version("1.0.0 || 2.0.0", "2.0.0"));
1635 assert!(!check_semver_version("1.0.0 || 2.0.0", "3.0.0"));
1636 }
1637
1638 #[test]
1639 fn test_wildcard_match_minor() {
1640 assert!(check_semver_version("1.*", "1.1.0"));
1641 assert!(check_semver_version("1.*, <2.0.0", "1.1.0"));
1642 }
1643
1644 #[test]
1645 fn test_wildcard_mismatch() {
1646 assert!(!check_semver_version("1.*, <2.0.0", "2.1.0"));
1647 assert!(!check_semver_version("1.*, <2.0.0", "2.0.0"));
1648 assert!(!check_semver_version("1.*, <2.*", "2.1.0"));
1649 assert!(!check_semver_version("1.*, <2.*", "2.0.0"));
1650
1651 assert!(!check_semver_version("1.*, >2.0.0", "1.1.0"));
1652 assert!(!check_semver_version("1.*, >2.0.0", "1.0.0"));
1653 assert!(!check_semver_version("1.*, >2.*", "1.1.0"));
1654 assert!(!check_semver_version("1.*, >2.*", "1.0.0"));
1655
1656 assert!(!check_semver_version("<1.5.0, >1.10.*", "1.6.0"));
1657 }
1658
1659 #[test]
1660 fn test_wildcard_match_major() {
1661 assert!(check_semver_version("2.*", "2.0.0"));
1662 }
1663
1664 #[test]
1665 fn test_wildcard_match_patch() {
1666 assert!(check_semver_version("1.0.*", "1.0.1"));
1667 }
1668
1669 #[test]
1670 fn test_invalid_inputs() {
1671 assert!(!check_semver_version("not.a.requirement", "1.0.0"));
1672 assert!(!check_semver_version("1.0.0", "not.a.version"));
1673 }
1674
1675 #[test]
1676 fn test_version_with_pre_release_and_build() {
1677 assert!(check_semver_version("1.0.0-alpha", "1.0.0-alpha+001"));
1678 }
1679
1680 #[test]
1682 fn test_pre_release_numeric_vs_alphanumeric() {
1683 assert!(!check_semver_version("^1.0.0-alpha.beta", "1.0.0-alpha.1"));
1684 assert!(check_semver_version("^1.0.0-alpha.1", "1.0.0-alpha.beta"));
1685 }
1686
1687 #[test]
1689 fn test_wildcard_any() {
1690 assert!(check_semver_version("*", "1.0.0"));
1691 assert!(check_semver_version("*", "1.0.0+build"));
1692 }
1693
1694 #[test]
1696 fn test_pre_release_lexicographic_ordering() {
1697 assert!(check_semver_version(
1698 "^1.0.0-alpha.alpha",
1699 "1.0.0-alpha.beta",
1700 ));
1701 assert!(!check_semver_version(
1702 "^1.0.0-alpha.beta",
1703 "1.0.0-alpha.alpha",
1704 ));
1705 }
1706
1707 #[test]
1709 fn test_wildcard_any_with_range() {
1710 assert!(!check_semver_version("*, <2.0.0", "1.0.0"));
1711 assert!(!check_semver_version("*, 1.0.0", "1.5.0"));
1712 }
1713 }
1714}