Skip to main content

rustfmt_nightly/config/
mod.rs

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