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    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
83    // Single line expressions and items
84    empty_item_single_line: EmptyItemSingleLine, false,
85        "Put empty-body functions and impls on a single line";
86    struct_lit_single_line: StructLitSingleLine, false,
87        "Put small struct literals on a single line";
88    fn_single_line: FnSingleLine, false, "Put single-expression functions on a single line";
89    where_single_line: WhereSingleLine, false, "Force where-clauses to be on a single line";
90
91    // Imports
92    imports_indent: ImportsIndent, false, "Indent of imports";
93    imports_layout: ImportsLayout, false, "Item layout inside a import block";
94    imports_granularity: ImportsGranularityConfig, false,
95        "Merge or split imports to the provided granularity";
96    group_imports: GroupImportsTacticConfig, false,
97        "Controls the strategy for how imports are grouped together";
98    merge_imports: MergeImports, false, "(deprecated: use imports_granularity instead)";
99
100    // Ordering
101    reorder_imports: ReorderImports, true, "Reorder import and extern crate statements \
102        alphabetically";
103    reorder_modules: ReorderModules, true, "Reorder module statements alphabetically in group";
104    reorder_impl_items: ReorderImplItems, false, "Reorder impl items";
105
106    // Spaces around punctuation
107    type_punctuation_density: TypePunctuationDensity, false,
108        "Determines if '+' or '=' are wrapped in spaces in the punctuation of types";
109    space_before_colon: SpaceBeforeColon, false, "Leave a space before the colon";
110    space_after_colon: SpaceAfterColon, false, "Leave a space after the colon";
111    spaces_around_ranges: SpacesAroundRanges, false, "Put spaces around the  .. and ..= range \
112        operators";
113    binop_separator: BinopSeparator, false,
114        "Where to put a binary operator when a binary expression goes multiline";
115
116    // Misc.
117    remove_nested_parens: RemoveNestedParens, true, "Remove nested parens";
118    combine_control_expr: CombineControlExpr, false, "Combine control expressions with function \
119        calls";
120    short_array_element_width_threshold: ShortArrayElementWidthThreshold, true,
121        "Width threshold for an array element to be considered short";
122    overflow_delimited_expr: OverflowDelimitedExpr, false,
123        "Allow trailing bracket/brace delimited expressions to overflow";
124    struct_field_align_threshold: StructFieldAlignThreshold, false,
125        "Align struct fields if their diffs fits within threshold";
126    enum_discrim_align_threshold: EnumDiscrimAlignThreshold, false,
127        "Align enum variants discrims, if their diffs fit within threshold";
128    match_arm_blocks: MatchArmBlocks, false, "Wrap the body of arms in blocks when it does not fit \
129        on the same line with the pattern of arms";
130    match_arm_leading_pipes: MatchArmLeadingPipeConfig, true,
131        "Determines whether leading pipes are emitted on match arms";
132    force_multiline_blocks: ForceMultilineBlocks, false,
133        "Force multiline closure bodies and match arms to be wrapped in a block";
134    fn_args_layout: FnArgsLayout, true,
135        "(deprecated: use fn_params_layout instead)";
136    fn_params_layout: FnParamsLayout, true,
137        "Control the layout of parameters in function signatures.";
138    brace_style: BraceStyleConfig, false, "Brace style for items";
139    control_brace_style: ControlBraceStyleConfig, false,
140        "Brace style for control flow constructs";
141    trailing_semicolon: TrailingSemicolon, false,
142        "Add trailing semicolon after break, continue and return";
143    trailing_comma: TrailingComma, false,
144        "How to handle trailing commas for lists";
145    match_block_trailing_comma: MatchBlockTrailingComma, true,
146        "Put a trailing comma after a block based match arm (non-block arms are not affected)";
147    blank_lines_upper_bound: BlankLinesUpperBound, false,
148        "Maximum number of blank lines which can be put between items";
149    blank_lines_lower_bound: BlankLinesLowerBound, false,
150        "Minimum number of blank lines which must be put between items";
151    edition: EditionConfig, true, "The edition of the parser (RFC 2052)";
152    style_edition: StyleEditionConfig, true, "The edition of the Style Guide (RFC 3338)";
153    version: VersionConfig, false, "Version of formatting rules";
154    inline_attribute_width: InlineAttributeWidth, false,
155        "Write an item and its attribute on the same line \
156        if their combined width is below a threshold";
157    format_generated_files: FormatGeneratedFiles, false, "Format generated files";
158    generated_marker_line_search_limit: GeneratedMarkerLineSearchLimit, false, "Number of lines to \
159        check for a `@generated` marker when `format_generated_files` is enabled";
160
161    // Options that can change the source code beyond whitespace/blocks (somewhat linty things)
162    merge_derives: MergeDerives, true, "Merge multiple `#[derive(...)]` into a single one";
163    use_try_shorthand: UseTryShorthand, true, "Replace uses of the try! macro by the ? shorthand";
164    use_field_init_shorthand: UseFieldInitShorthand, true, "Use field initialization shorthand if \
165        possible";
166    force_explicit_abi: ForceExplicitAbi, true, "Always print the abi for extern items";
167    condense_wildcard_suffixes: CondenseWildcardSuffixes, false, "Replace strings of _ wildcards \
168        by a single .. in tuple patterns";
169
170    // Control options (changes the operation of rustfmt, rather than the formatting)
171    color: ColorConfig, false,
172        "What Color option to use when none is supplied: Always, Never, Auto";
173    required_version: RequiredVersion, false,
174        "Require a specific version of rustfmt";
175    unstable_features: UnstableFeatures, false,
176            "Enables unstable features. Only available on nightly channel";
177    disable_all_formatting: DisableAllFormatting, true, "Don't reformat anything";
178    skip_children: SkipChildren, false, "Don't reformat out of line modules";
179    hide_parse_errors: HideParseErrors, false, "Hide errors from the parser";
180    show_parse_errors: ShowParseErrors, false, "Show errors from the parser (unstable)";
181    error_on_line_overflow: ErrorOnLineOverflow, false, "Error if unable to get all lines within \
182        max_width";
183    error_on_unformatted: ErrorOnUnformatted, false,
184        "Error if unable to get comments or string literals within max_width, \
185         or they are left with trailing whitespaces";
186    ignore: Ignore, false,
187        "Skip formatting the specified files and directories";
188
189    // Not user-facing
190    verbose: Verbose, false, "How much to information to emit to the user";
191    file_lines: FileLinesConfig, false,
192        "Lines to format; this is not supported in rustfmt.toml, and can only be specified \
193         via the --file-lines option";
194    emit_mode: EmitModeConfig, false,
195        "What emit Mode to use when none is supplied";
196    make_backup: MakeBackup, false, "Backup changed files";
197    print_misformatted_file_names: PrintMisformattedFileNames, true,
198        "Prints the names of mismatched files that were formatted. Prints the names of \
199         files that would be formatted when used with `--check` mode. ";
200}
201
202#[derive(Error, Debug)]
203#[error("Could not output config: {0}")]
204pub struct ToTomlError(toml::ser::Error);
205
206impl PartialConfig {
207    pub fn to_toml(&self) -> Result<String, ToTomlError> {
208        // Non-user-facing options can't be specified in TOML
209        let mut cloned = self.clone();
210        cloned.file_lines = None;
211        cloned.verbose = None;
212        cloned.width_heuristics = None;
213        cloned.print_misformatted_file_names = None;
214        cloned.merge_imports = None;
215        cloned.fn_args_layout = None;
216        cloned.hide_parse_errors = None;
217
218        ::toml::to_string(&cloned).map_err(ToTomlError)
219    }
220
221    pub(super) fn to_parsed_config(
222        self,
223        style_edition_override: Option<StyleEdition>,
224        edition_override: Option<Edition>,
225        version_override: Option<Version>,
226        dir: &Path,
227    ) -> Config {
228        Config::default_for_possible_style_edition(
229            style_edition_override.or(self.style_edition),
230            edition_override.or(self.edition),
231            version_override.or(self.version),
232        )
233        .fill_from_parsed_config(self, dir)
234    }
235}
236
237impl Config {
238    pub fn default_for_possible_style_edition(
239        style_edition: Option<StyleEdition>,
240        edition: Option<Edition>,
241        version: Option<Version>,
242    ) -> Config {
243        // Ensures the configuration defaults associated with Style Editions
244        // follow the precedence set in
245        // https://rust-lang.github.io/rfcs/3338-style-evolution.html
246        // 'version' is a legacy alias for 'style_edition' that we'll support
247        // for some period of time
248        // FIXME(calebcartwright) - remove 'version' at some point
249        match (style_edition, version, edition) {
250            (Some(se), _, _) => Self::default_with_style_edition(se),
251            (None, Some(Version::Two), _) => {
252                Self::default_with_style_edition(StyleEdition::Edition2024)
253            }
254            (None, Some(Version::One), _) => {
255                Self::default_with_style_edition(StyleEdition::Edition2015)
256            }
257            (None, None, Some(e)) => Self::default_with_style_edition(e.into()),
258            (None, None, None) => Config::default(),
259        }
260    }
261
262    pub(crate) fn version_meets_requirement(&self) -> bool {
263        if self.was_set().required_version() {
264            let version = env!("CARGO_PKG_VERSION");
265            let required_version = self.required_version();
266            if version != required_version {
267                println!(
268                    "Error: rustfmt version ({version}) doesn't match the required version \
269({required_version})"
270                );
271                return false;
272            }
273        }
274
275        true
276    }
277
278    /// Constructs a `Config` from the toml file specified at `file_path`.
279    ///
280    /// This method only looks at the provided path, for a method that
281    /// searches parents for a `rustfmt.toml` see `from_resolved_toml_path`.
282    ///
283    /// Returns a `Config` if the config could be read and parsed from
284    /// the file, otherwise errors.
285    pub(super) fn from_toml_path(
286        file_path: &Path,
287        edition: Option<Edition>,
288        style_edition: Option<StyleEdition>,
289        version: Option<Version>,
290    ) -> Result<Config, Error> {
291        let mut file = File::open(&file_path)?;
292        let mut toml = String::new();
293        file.read_to_string(&mut toml)?;
294        Config::from_toml_for_style_edition(&toml, file_path, edition, style_edition, version)
295            .map_err(|err| Error::new(ErrorKind::InvalidData, err))
296    }
297
298    /// Resolves the config for input in `dir`.
299    ///
300    /// Searches for `rustfmt.toml` beginning with `dir`, and
301    /// recursively checking parents of `dir` if no config file is found.
302    /// If no config file exists in `dir` or in any parent, a
303    /// default `Config` will be returned (and the returned path will be empty).
304    ///
305    /// Returns the `Config` to use, and the path of the project file if there was
306    /// one.
307    pub(super) fn from_resolved_toml_path(
308        dir: &Path,
309        edition: Option<Edition>,
310        style_edition: Option<StyleEdition>,
311        version: Option<Version>,
312    ) -> Result<(Config, Option<PathBuf>), Error> {
313        /// Try to find a project file in the given directory and its parents.
314        /// Returns the path of the nearest project file if one exists,
315        /// or `None` if no project file was found.
316        fn resolve_project_file(dir: &Path) -> Result<Option<PathBuf>, Error> {
317            let mut current = if dir.is_relative() {
318                env::current_dir()?.join(dir)
319            } else {
320                dir.to_path_buf()
321            };
322
323            current = fs::canonicalize(current)?;
324
325            loop {
326                match get_toml_path(&current) {
327                    Ok(Some(path)) => return Ok(Some(path)),
328                    Err(e) => return Err(e),
329                    _ => (),
330                }
331
332                // If the current directory has no parent, we're done searching.
333                if !current.pop() {
334                    break;
335                }
336            }
337
338            // If nothing was found, check in the home directory.
339            if let Some(home_dir) = dirs::home_dir() {
340                if let Some(path) = get_toml_path(&home_dir)? {
341                    return Ok(Some(path));
342                }
343            }
344
345            // If none was found there either, check in the user's configuration directory.
346            if let Some(mut config_dir) = dirs::config_dir() {
347                config_dir.push("rustfmt");
348                if let Some(path) = get_toml_path(&config_dir)? {
349                    return Ok(Some(path));
350                }
351            }
352
353            Ok(None)
354        }
355
356        match resolve_project_file(dir)? {
357            None => Ok((
358                Config::default_for_possible_style_edition(style_edition, edition, version),
359                None,
360            )),
361            Some(path) => Config::from_toml_path(&path, edition, style_edition, version)
362                .map(|config| (config, Some(path))),
363        }
364    }
365
366    #[allow(dead_code)]
367    pub(super) fn from_toml(toml: &str, file_path: &Path) -> Result<Config, String> {
368        Self::from_toml_for_style_edition(toml, file_path, None, None, None)
369    }
370
371    pub(crate) fn from_toml_for_style_edition(
372        toml: &str,
373        file_path: &Path,
374        edition: Option<Edition>,
375        style_edition: Option<StyleEdition>,
376        version: Option<Version>,
377    ) -> Result<Config, String> {
378        let parsed: ::toml::Value = toml
379            .parse()
380            .map_err(|e| format!("Could not parse TOML: {}", e))?;
381        let mut err = String::new();
382        let table = parsed
383            .as_table()
384            .ok_or_else(|| String::from("Parsed config was not table"))?;
385        for key in table.keys() {
386            if !Config::is_valid_name(key) {
387                let msg = &format!("Warning: Unknown configuration option `{key}`\n");
388                err.push_str(msg)
389            }
390        }
391
392        match parsed.try_into::<PartialConfig>() {
393            Ok(parsed_config) => {
394                if !err.is_empty() {
395                    eprint!("{err}");
396                }
397                let dir = file_path.parent().ok_or_else(|| {
398                    format!("failed to get parent directory for {}", file_path.display())
399                })?;
400
401                Ok(parsed_config.to_parsed_config(style_edition, edition, version, dir))
402            }
403            Err(e) => {
404                let err_msg = format!(
405                    "The file `{}` failed to parse.\nError details: {e}",
406                    file_path.display()
407                );
408                err.push_str(&err_msg);
409                Err(err_msg)
410            }
411        }
412    }
413}
414
415/// Loads a config by checking the client-supplied options and if appropriate, the
416/// file system (including searching the file system for overrides).
417pub fn load_config<O: CliOptions>(
418    file_path: Option<&Path>,
419    options: Option<O>,
420) -> Result<(Config, Option<PathBuf>), Error> {
421    let (over_ride, edition, style_edition, version) = match options {
422        Some(ref opts) => (
423            config_path(opts)?,
424            opts.edition(),
425            opts.style_edition(),
426            opts.version(),
427        ),
428        None => (None, None, None, None),
429    };
430
431    let result = if let Some(over_ride) = over_ride {
432        Config::from_toml_path(over_ride.as_ref(), edition, style_edition, version)
433            .map(|p| (p, Some(over_ride.to_owned())))
434    } else if let Some(file_path) = file_path {
435        Config::from_resolved_toml_path(file_path, edition, style_edition, version)
436    } else {
437        Ok((
438            Config::default_for_possible_style_edition(style_edition, edition, version),
439            None,
440        ))
441    };
442
443    result.map(|(mut c, p)| {
444        if let Some(options) = options {
445            options.apply_to(&mut c);
446        }
447        (c, p)
448    })
449}
450
451// Check for the presence of known config file names (`rustfmt.toml`, `.rustfmt.toml`) in `dir`
452//
453// Return the path if a config file exists, empty if no file exists, and Error for IO errors
454fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
455    const CONFIG_FILE_NAMES: [&str; 2] = [".rustfmt.toml", "rustfmt.toml"];
456    for config_file_name in &CONFIG_FILE_NAMES {
457        let config_file = dir.join(config_file_name);
458        match fs::metadata(&config_file) {
459            // Only return if it's a file to handle the unlikely situation of a directory named
460            // `rustfmt.toml`.
461            Ok(ref md) if md.is_file() => return Ok(Some(config_file.canonicalize()?)),
462            // Return the error if it's something other than `NotFound`; otherwise we didn't
463            // find the project file yet, and continue searching.
464            Err(e) => {
465                if e.kind() != ErrorKind::NotFound {
466                    let ctx = format!("Failed to get metadata for config file {:?}", &config_file);
467                    let err = anyhow::Error::new(e).context(ctx);
468                    return Err(Error::new(ErrorKind::Other, err));
469                }
470            }
471            _ => {}
472        }
473    }
474    Ok(None)
475}
476
477fn config_path(options: &dyn CliOptions) -> Result<Option<PathBuf>, Error> {
478    let config_path_not_found = |path: &str| -> Result<Option<PathBuf>, Error> {
479        Err(Error::new(
480            ErrorKind::NotFound,
481            format!(
482                "Error: unable to find a config file for the given path: `{}`",
483                path
484            ),
485        ))
486    };
487
488    // Read the config_path and convert to parent dir if a file is provided.
489    // If a config file cannot be found from the given path, return error.
490    match options.config_path() {
491        Some(path) if !path.exists() => config_path_not_found(path.to_str().unwrap()),
492        Some(path) if path.is_dir() => {
493            let config_file_path = get_toml_path(path)?;
494            if config_file_path.is_some() {
495                Ok(config_file_path)
496            } else {
497                config_path_not_found(path.to_str().unwrap())
498            }
499        }
500        Some(path) => Ok(Some(
501            // Canonicalize only after checking above that the `path.exists()`.
502            path.canonicalize()?,
503        )),
504        None => Ok(None),
505    }
506}
507
508#[cfg(test)]
509mod test {
510    use super::*;
511    use std::str;
512
513    use crate::config::macro_names::{MacroName, MacroSelectors};
514    use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
515
516    #[allow(dead_code)]
517    mod mock {
518        use super::super::*;
519        use rustfmt_config_proc_macro::config_type;
520
521        #[config_type]
522        pub(crate) enum PartiallyUnstableOption {
523            V1,
524            V2,
525            #[unstable_variant]
526            V3,
527        }
528
529        config_option_with_style_edition_default!(
530            StableOption, bool, _ => false;
531            UnstableOption, bool, _ => false;
532            PartiallyUnstable, PartiallyUnstableOption, _ => PartiallyUnstableOption::V1;
533        );
534
535        create_config! {
536            // Options that are used by the generated functions
537            max_width: MaxWidth, true, "Maximum width of each line";
538            required_version: RequiredVersion, false, "Require a specific version of rustfmt.";
539            ignore: Ignore, false, "Skip formatting the specified files and directories.";
540            verbose: Verbose, false, "How much to information to emit to the user";
541            file_lines: FileLinesConfig, false,
542                "Lines to format; this is not supported in rustfmt.toml, and can only be specified \
543                    via the --file-lines option";
544
545            // merge_imports deprecation
546            imports_granularity: ImportsGranularityConfig, false, "Merge imports";
547            merge_imports: MergeImports, false, "(deprecated: use imports_granularity instead)";
548
549            // fn_args_layout renamed to fn_params_layout
550            fn_args_layout: FnArgsLayout, true, "(deprecated: use fn_params_layout instead)";
551            fn_params_layout: FnParamsLayout, true,
552                "Control the layout of parameters in a function signatures.";
553
554            // hide_parse_errors renamed to show_parse_errors
555            hide_parse_errors: HideParseErrors, false,
556                "(deprecated: use show_parse_errors instead)";
557            show_parse_errors: ShowParseErrors, false,
558                "Show errors from the parser (unstable)";
559
560
561            // Width Heuristics
562            use_small_heuristics: UseSmallHeuristics, true,
563                "Whether to use different formatting for items and \
564                 expressions if they satisfy a heuristic notion of 'small'.";
565            width_heuristics: WidthHeuristicsConfig, false, "'small' heuristic values";
566
567            fn_call_width: FnCallWidth, true, "Maximum width of the args of a function call before \
568                falling back to vertical formatting.";
569            attr_fn_like_width: AttrFnLikeWidth, true, "Maximum width of the args of a \
570                function-like attributes before falling back to vertical formatting.";
571            struct_lit_width: StructLitWidth, true, "Maximum width in the body of a struct lit \
572                before falling back to vertical formatting.";
573            struct_variant_width: StructVariantWidth, true, "Maximum width in the body of a struct \
574                variant before falling back to vertical formatting.";
575            array_width: ArrayWidth, true,  "Maximum width of an array literal before falling \
576                back to vertical formatting.";
577            chain_width: ChainWidth, true, "Maximum length of a chain to fit on a single line.";
578            single_line_if_else_max_width: SingleLineIfElseMaxWidth, true, "Maximum line length \
579                for single line if-else expressions. A value of zero means always break if-else \
580                expressions.";
581            single_line_let_else_max_width: SingleLineLetElseMaxWidth, false, "Maximum line length \
582                for single line let-else statements. A value of zero means always format the \
583                divergent `else` block over multiple lines.";
584
585            // Options that are used by the tests
586            stable_option: StableOption, true, "A stable option";
587            unstable_option: UnstableOption, false, "An unstable option";
588            partially_unstable_option: PartiallyUnstable, true, "A partially unstable option";
589            edition: EditionConfig, true, "blah";
590            style_edition: StyleEditionConfig, true, "blah";
591            version: VersionConfig, false, "blah blah"
592        }
593
594        #[cfg(test)]
595        mod partially_unstable_option {
596            use super::{Config, PartialConfig, PartiallyUnstableOption};
597            use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
598            use std::path::Path;
599
600            /// From the config file, we can fill with a stable variant
601            #[test]
602            fn test_from_toml_stable_value() {
603                let toml = r#"
604                    partially_unstable_option = "V2"
605                "#;
606                let partial_config: PartialConfig = toml::from_str(toml).unwrap();
607                let config = Config::default();
608                let config = config.fill_from_parsed_config(partial_config, Path::new(""));
609                assert_eq!(
610                    config.partially_unstable_option(),
611                    PartiallyUnstableOption::V2
612                );
613            }
614
615            /// From the config file, we cannot fill with an unstable variant (stable only)
616            #[stable_only_test]
617            #[test]
618            fn test_from_toml_unstable_value_on_stable() {
619                let toml = r#"
620                    partially_unstable_option = "V3"
621                "#;
622                let partial_config: PartialConfig = toml::from_str(toml).unwrap();
623                let config = Config::default();
624                let config = config.fill_from_parsed_config(partial_config, Path::new(""));
625                assert_eq!(
626                    config.partially_unstable_option(),
627                    // default value from config, i.e. fill failed
628                    PartiallyUnstableOption::V1
629                );
630            }
631
632            /// From the config file, we can fill with an unstable variant (nightly only)
633            #[nightly_only_test]
634            #[test]
635            fn test_from_toml_unstable_value_on_nightly() {
636                let toml = r#"
637                    partially_unstable_option = "V3"
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::V3
645                );
646            }
647        }
648    }
649
650    #[test]
651    fn test_config_set() {
652        let mut config = Config::default();
653        config.set().verbose(Verbosity::Quiet);
654        assert_eq!(config.verbose(), Verbosity::Quiet);
655        config.set().verbose(Verbosity::Normal);
656        assert_eq!(config.verbose(), Verbosity::Normal);
657    }
658
659    #[test]
660    fn test_config_used_to_toml() {
661        let config = Config::default();
662
663        let merge_derives = config.merge_derives();
664        let skip_children = config.skip_children();
665
666        let used_options = config.used_options();
667        let toml = used_options.to_toml().unwrap();
668        assert_eq!(
669            toml,
670            format!("merge_derives = {merge_derives}\nskip_children = {skip_children}\n",)
671        );
672    }
673
674    #[test]
675    fn test_was_set() {
676        let config = Config::from_toml("hard_tabs = true", Path::new("./rustfmt.toml")).unwrap();
677
678        assert_eq!(config.was_set().hard_tabs(), true);
679        assert_eq!(config.was_set().verbose(), false);
680    }
681
682    const PRINT_DOCS_STABLE_OPTION: &str = "stable_option <boolean> Default: false";
683    const PRINT_DOCS_UNSTABLE_OPTION: &str = "unstable_option <boolean> Default: false (unstable)";
684    const PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION: &str =
685        "partially_unstable_option [V1|V2|V3 (unstable)] Default: V1";
686
687    #[test]
688    fn test_print_docs_exclude_unstable() {
689        use self::mock::Config;
690
691        let mut output = Vec::new();
692        Config::print_docs(&mut output, false);
693
694        let s = str::from_utf8(&output).unwrap();
695        assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true);
696        assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), false);
697        assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
698    }
699
700    #[test]
701    fn test_print_docs_include_unstable() {
702        use self::mock::Config;
703
704        let mut output = Vec::new();
705        Config::print_docs(&mut output, true);
706
707        let s = str::from_utf8(&output).unwrap();
708        assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true);
709        assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), true);
710        assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
711    }
712
713    #[test]
714    fn test_dump_default_config() {
715        let default_config = format!(
716            r#"max_width = 100
717hard_tabs = false
718tab_spaces = 4
719newline_style = "Auto"
720indent_style = "Block"
721use_small_heuristics = "Default"
722fn_call_width = 60
723attr_fn_like_width = 70
724struct_lit_width = 18
725struct_variant_width = 35
726array_width = 60
727chain_width = 60
728single_line_if_else_max_width = 50
729single_line_let_else_max_width = 50
730wrap_comments = false
731format_code_in_doc_comments = false
732doc_comment_code_block_width = 100
733comment_width = 80
734normalize_comments = false
735normalize_doc_attributes = false
736format_strings = false
737format_macro_matchers = false
738format_macro_bodies = true
739skip_macro_invocations = []
740hex_literal_case = "Preserve"
741empty_item_single_line = true
742struct_lit_single_line = true
743fn_single_line = false
744where_single_line = false
745imports_indent = "Block"
746imports_layout = "Mixed"
747imports_granularity = "Preserve"
748group_imports = "Preserve"
749reorder_imports = true
750reorder_modules = true
751reorder_impl_items = false
752type_punctuation_density = "Wide"
753space_before_colon = false
754space_after_colon = true
755spaces_around_ranges = false
756binop_separator = "Front"
757remove_nested_parens = true
758combine_control_expr = true
759short_array_element_width_threshold = 10
760overflow_delimited_expr = false
761struct_field_align_threshold = 0
762enum_discrim_align_threshold = 0
763match_arm_blocks = true
764match_arm_leading_pipes = "Never"
765force_multiline_blocks = false
766fn_params_layout = "Tall"
767brace_style = "SameLineWhere"
768control_brace_style = "AlwaysSameLine"
769trailing_semicolon = true
770trailing_comma = "Vertical"
771match_block_trailing_comma = false
772blank_lines_upper_bound = 1
773blank_lines_lower_bound = 0
774edition = "2015"
775style_edition = "2015"
776version = "One"
777inline_attribute_width = 0
778format_generated_files = true
779generated_marker_line_search_limit = 5
780merge_derives = true
781use_try_shorthand = false
782use_field_init_shorthand = false
783force_explicit_abi = true
784condense_wildcard_suffixes = false
785color = "Auto"
786required_version = "{}"
787unstable_features = false
788disable_all_formatting = false
789skip_children = false
790show_parse_errors = true
791error_on_line_overflow = false
792error_on_unformatted = false
793ignore = []
794emit_mode = "Files"
795make_backup = false
796"#,
797            env!("CARGO_PKG_VERSION")
798        );
799        let toml = Config::default().all_options().to_toml().unwrap();
800        assert_eq!(&toml, &default_config);
801    }
802
803    #[test]
804    fn test_dump_style_edition_2024_config() {
805        let edition_2024_config = format!(
806            r#"max_width = 100
807hard_tabs = false
808tab_spaces = 4
809newline_style = "Auto"
810indent_style = "Block"
811use_small_heuristics = "Default"
812fn_call_width = 60
813attr_fn_like_width = 70
814struct_lit_width = 18
815struct_variant_width = 35
816array_width = 60
817chain_width = 60
818single_line_if_else_max_width = 50
819single_line_let_else_max_width = 50
820wrap_comments = false
821format_code_in_doc_comments = false
822doc_comment_code_block_width = 100
823comment_width = 80
824normalize_comments = false
825normalize_doc_attributes = false
826format_strings = false
827format_macro_matchers = false
828format_macro_bodies = true
829skip_macro_invocations = []
830hex_literal_case = "Preserve"
831empty_item_single_line = true
832struct_lit_single_line = true
833fn_single_line = false
834where_single_line = false
835imports_indent = "Block"
836imports_layout = "Mixed"
837imports_granularity = "Preserve"
838group_imports = "Preserve"
839reorder_imports = true
840reorder_modules = true
841reorder_impl_items = false
842type_punctuation_density = "Wide"
843space_before_colon = false
844space_after_colon = true
845spaces_around_ranges = false
846binop_separator = "Front"
847remove_nested_parens = true
848combine_control_expr = true
849short_array_element_width_threshold = 10
850overflow_delimited_expr = false
851struct_field_align_threshold = 0
852enum_discrim_align_threshold = 0
853match_arm_blocks = true
854match_arm_leading_pipes = "Never"
855force_multiline_blocks = false
856fn_params_layout = "Tall"
857brace_style = "SameLineWhere"
858control_brace_style = "AlwaysSameLine"
859trailing_semicolon = true
860trailing_comma = "Vertical"
861match_block_trailing_comma = false
862blank_lines_upper_bound = 1
863blank_lines_lower_bound = 0
864edition = "2015"
865style_edition = "2024"
866version = "Two"
867inline_attribute_width = 0
868format_generated_files = true
869generated_marker_line_search_limit = 5
870merge_derives = true
871use_try_shorthand = false
872use_field_init_shorthand = false
873force_explicit_abi = true
874condense_wildcard_suffixes = false
875color = "Auto"
876required_version = "{}"
877unstable_features = false
878disable_all_formatting = false
879skip_children = false
880show_parse_errors = true
881error_on_line_overflow = false
882error_on_unformatted = false
883ignore = []
884emit_mode = "Files"
885make_backup = false
886"#,
887            env!("CARGO_PKG_VERSION")
888        );
889        let toml = Config::default_with_style_edition(StyleEdition::Edition2024)
890            .all_options()
891            .to_toml()
892            .unwrap();
893        assert_eq!(&toml, &edition_2024_config);
894    }
895
896    #[test]
897    fn test_editions_2015_2018_2021_identical() {
898        let get_edition_toml = |style_edition: StyleEdition| {
899            Config::default_with_style_edition(style_edition)
900                .all_options()
901                .to_toml()
902                .unwrap()
903        };
904        let edition2015 = get_edition_toml(StyleEdition::Edition2015);
905        let edition2018 = get_edition_toml(StyleEdition::Edition2018);
906        let edition2021 = get_edition_toml(StyleEdition::Edition2021);
907        assert_eq!(edition2015, edition2018);
908        assert_eq!(edition2018, edition2021);
909    }
910
911    #[stable_only_test]
912    #[test]
913    fn test_as_not_nightly_channel() {
914        let mut config = Config::default();
915        assert_eq!(config.was_set().unstable_features(), false);
916        config.set().unstable_features(true);
917        assert_eq!(config.was_set().unstable_features(), false);
918    }
919
920    #[nightly_only_test]
921    #[test]
922    fn test_as_nightly_channel() {
923        let mut config = Config::default();
924        config.set().unstable_features(true);
925        // When we don't set the config from toml or command line options it
926        // doesn't get marked as set by the user.
927        assert_eq!(config.was_set().unstable_features(), false);
928        config.set().unstable_features(true);
929        assert_eq!(config.unstable_features(), true);
930    }
931
932    #[nightly_only_test]
933    #[test]
934    fn test_unstable_from_toml() {
935        let config =
936            Config::from_toml("unstable_features = true", Path::new("./rustfmt.toml")).unwrap();
937        assert_eq!(config.was_set().unstable_features(), true);
938        assert_eq!(config.unstable_features(), true);
939    }
940
941    #[test]
942    fn test_set_cli() {
943        let mut config = Config::default();
944        assert_eq!(config.was_set().edition(), false);
945        assert_eq!(config.was_set_cli().edition(), false);
946        config.set().edition(Edition::Edition2021);
947        assert_eq!(config.was_set().edition(), false);
948        assert_eq!(config.was_set_cli().edition(), false);
949        config.set_cli().edition(Edition::Edition2021);
950        assert_eq!(config.was_set().edition(), false);
951        assert_eq!(config.was_set_cli().edition(), true);
952        assert_eq!(config.was_set_cli().emit_mode(), false);
953    }
954
955    #[cfg(test)]
956    mod deprecated_option_merge_imports {
957        use super::*;
958
959        #[nightly_only_test]
960        #[test]
961        fn test_old_option_set() {
962            let toml = r#"
963                unstable_features = true
964                merge_imports = true
965            "#;
966            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
967            assert_eq!(config.imports_granularity(), ImportGranularity::Crate);
968        }
969
970        #[nightly_only_test]
971        #[test]
972        fn test_both_set() {
973            let toml = r#"
974                unstable_features = true
975                merge_imports = true
976                imports_granularity = "Preserve"
977            "#;
978            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
979            assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
980        }
981
982        #[nightly_only_test]
983        #[test]
984        fn test_new_overridden() {
985            let toml = r#"
986                unstable_features = true
987                merge_imports = true
988            "#;
989            let mut config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
990            config.override_value("imports_granularity", "Preserve");
991            assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
992        }
993
994        #[nightly_only_test]
995        #[test]
996        fn test_old_overridden() {
997            let toml = r#"
998                unstable_features = true
999                imports_granularity = "Module"
1000            "#;
1001            let mut config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1002            config.override_value("merge_imports", "true");
1003            // no effect: the new option always takes precedence
1004            assert_eq!(config.imports_granularity(), ImportGranularity::Module);
1005        }
1006    }
1007
1008    #[cfg(test)]
1009    mod use_small_heuristics {
1010        use super::*;
1011
1012        #[test]
1013        fn test_default_sets_correct_widths() {
1014            let toml = r#"
1015                use_small_heuristics = "Default"
1016                max_width = 200
1017            "#;
1018            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1019            assert_eq!(config.array_width(), 120);
1020            assert_eq!(config.attr_fn_like_width(), 140);
1021            assert_eq!(config.chain_width(), 120);
1022            assert_eq!(config.fn_call_width(), 120);
1023            assert_eq!(config.single_line_if_else_max_width(), 100);
1024            assert_eq!(config.struct_lit_width(), 36);
1025            assert_eq!(config.struct_variant_width(), 70);
1026        }
1027
1028        #[test]
1029        fn test_max_sets_correct_widths() {
1030            let toml = r#"
1031                use_small_heuristics = "Max"
1032                max_width = 120
1033            "#;
1034            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1035            assert_eq!(config.array_width(), 120);
1036            assert_eq!(config.attr_fn_like_width(), 120);
1037            assert_eq!(config.chain_width(), 120);
1038            assert_eq!(config.fn_call_width(), 120);
1039            assert_eq!(config.single_line_if_else_max_width(), 120);
1040            assert_eq!(config.struct_lit_width(), 120);
1041            assert_eq!(config.struct_variant_width(), 120);
1042        }
1043
1044        #[test]
1045        fn test_off_sets_correct_widths() {
1046            let toml = r#"
1047                use_small_heuristics = "Off"
1048                max_width = 100
1049            "#;
1050            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1051            assert_eq!(config.array_width(), usize::MAX);
1052            assert_eq!(config.attr_fn_like_width(), usize::MAX);
1053            assert_eq!(config.chain_width(), usize::MAX);
1054            assert_eq!(config.fn_call_width(), usize::MAX);
1055            assert_eq!(config.single_line_if_else_max_width(), 0);
1056            assert_eq!(config.struct_lit_width(), 0);
1057            assert_eq!(config.struct_variant_width(), 0);
1058        }
1059
1060        #[test]
1061        fn test_override_works_with_default() {
1062            let toml = r#"
1063                use_small_heuristics = "Default"
1064                array_width = 20
1065                attr_fn_like_width = 40
1066                chain_width = 20
1067                fn_call_width = 90
1068                single_line_if_else_max_width = 40
1069                struct_lit_width = 30
1070                struct_variant_width = 34
1071            "#;
1072            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1073            assert_eq!(config.array_width(), 20);
1074            assert_eq!(config.attr_fn_like_width(), 40);
1075            assert_eq!(config.chain_width(), 20);
1076            assert_eq!(config.fn_call_width(), 90);
1077            assert_eq!(config.single_line_if_else_max_width(), 40);
1078            assert_eq!(config.struct_lit_width(), 30);
1079            assert_eq!(config.struct_variant_width(), 34);
1080        }
1081
1082        #[test]
1083        fn test_override_with_max() {
1084            let toml = r#"
1085                use_small_heuristics = "Max"
1086                array_width = 20
1087                attr_fn_like_width = 40
1088                chain_width = 20
1089                fn_call_width = 90
1090                single_line_if_else_max_width = 40
1091                struct_lit_width = 30
1092                struct_variant_width = 34
1093            "#;
1094            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1095            assert_eq!(config.array_width(), 20);
1096            assert_eq!(config.attr_fn_like_width(), 40);
1097            assert_eq!(config.chain_width(), 20);
1098            assert_eq!(config.fn_call_width(), 90);
1099            assert_eq!(config.single_line_if_else_max_width(), 40);
1100            assert_eq!(config.struct_lit_width(), 30);
1101            assert_eq!(config.struct_variant_width(), 34);
1102        }
1103
1104        #[test]
1105        fn test_override_with_off() {
1106            let toml = r#"
1107                use_small_heuristics = "Off"
1108                array_width = 20
1109                attr_fn_like_width = 40
1110                chain_width = 20
1111                fn_call_width = 90
1112                single_line_if_else_max_width = 40
1113                struct_lit_width = 30
1114                struct_variant_width = 34
1115            "#;
1116            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1117            assert_eq!(config.array_width(), 20);
1118            assert_eq!(config.attr_fn_like_width(), 40);
1119            assert_eq!(config.chain_width(), 20);
1120            assert_eq!(config.fn_call_width(), 90);
1121            assert_eq!(config.single_line_if_else_max_width(), 40);
1122            assert_eq!(config.struct_lit_width(), 30);
1123            assert_eq!(config.struct_variant_width(), 34);
1124        }
1125
1126        #[test]
1127        fn test_fn_call_width_config_exceeds_max_width() {
1128            let toml = r#"
1129                max_width = 90
1130                fn_call_width = 95
1131            "#;
1132            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1133            assert_eq!(config.fn_call_width(), 90);
1134        }
1135
1136        #[test]
1137        fn test_attr_fn_like_width_config_exceeds_max_width() {
1138            let toml = r#"
1139                max_width = 80
1140                attr_fn_like_width = 90
1141            "#;
1142            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1143            assert_eq!(config.attr_fn_like_width(), 80);
1144        }
1145
1146        #[test]
1147        fn test_struct_lit_config_exceeds_max_width() {
1148            let toml = r#"
1149                max_width = 78
1150                struct_lit_width = 90
1151            "#;
1152            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1153            assert_eq!(config.struct_lit_width(), 78);
1154        }
1155
1156        #[test]
1157        fn test_struct_variant_width_config_exceeds_max_width() {
1158            let toml = r#"
1159                max_width = 80
1160                struct_variant_width = 90
1161            "#;
1162            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1163            assert_eq!(config.struct_variant_width(), 80);
1164        }
1165
1166        #[test]
1167        fn test_array_width_config_exceeds_max_width() {
1168            let toml = r#"
1169                max_width = 60
1170                array_width = 80
1171            "#;
1172            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1173            assert_eq!(config.array_width(), 60);
1174        }
1175
1176        #[test]
1177        fn test_chain_width_config_exceeds_max_width() {
1178            let toml = r#"
1179                max_width = 80
1180                chain_width = 90
1181            "#;
1182            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1183            assert_eq!(config.chain_width(), 80);
1184        }
1185
1186        #[test]
1187        fn test_single_line_if_else_max_width_config_exceeds_max_width() {
1188            let toml = r#"
1189                max_width = 70
1190                single_line_if_else_max_width = 90
1191            "#;
1192            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1193            assert_eq!(config.single_line_if_else_max_width(), 70);
1194        }
1195
1196        #[test]
1197        fn test_override_fn_call_width_exceeds_max_width() {
1198            let mut config = Config::default();
1199            config.override_value("fn_call_width", "101");
1200            assert_eq!(config.fn_call_width(), 100);
1201        }
1202
1203        #[test]
1204        fn test_override_attr_fn_like_width_exceeds_max_width() {
1205            let mut config = Config::default();
1206            config.override_value("attr_fn_like_width", "101");
1207            assert_eq!(config.attr_fn_like_width(), 100);
1208        }
1209
1210        #[test]
1211        fn test_override_struct_lit_exceeds_max_width() {
1212            let mut config = Config::default();
1213            config.override_value("struct_lit_width", "101");
1214            assert_eq!(config.struct_lit_width(), 100);
1215        }
1216
1217        #[test]
1218        fn test_override_struct_variant_width_exceeds_max_width() {
1219            let mut config = Config::default();
1220            config.override_value("struct_variant_width", "101");
1221            assert_eq!(config.struct_variant_width(), 100);
1222        }
1223
1224        #[test]
1225        fn test_override_array_width_exceeds_max_width() {
1226            let mut config = Config::default();
1227            config.override_value("array_width", "101");
1228            assert_eq!(config.array_width(), 100);
1229        }
1230
1231        #[test]
1232        fn test_override_chain_width_exceeds_max_width() {
1233            let mut config = Config::default();
1234            config.override_value("chain_width", "101");
1235            assert_eq!(config.chain_width(), 100);
1236        }
1237
1238        #[test]
1239        fn test_override_single_line_if_else_max_width_exceeds_max_width() {
1240            let mut config = Config::default();
1241            config.override_value("single_line_if_else_max_width", "101");
1242            assert_eq!(config.single_line_if_else_max_width(), 100);
1243        }
1244    }
1245
1246    #[cfg(test)]
1247    mod partially_unstable_option {
1248        use super::mock::{Config, PartiallyUnstableOption};
1249
1250        /// From the command line, we can override with a stable variant.
1251        #[test]
1252        fn test_override_stable_value() {
1253            let mut config = Config::default();
1254            config.override_value("partially_unstable_option", "V2");
1255            assert_eq!(
1256                config.partially_unstable_option(),
1257                PartiallyUnstableOption::V2
1258            );
1259        }
1260
1261        /// From the command line, we can override with an unstable variant.
1262        #[test]
1263        fn test_override_unstable_value() {
1264            let mut config = Config::default();
1265            config.override_value("partially_unstable_option", "V3");
1266            assert_eq!(
1267                config.partially_unstable_option(),
1268                PartiallyUnstableOption::V3
1269            );
1270        }
1271    }
1272
1273    #[test]
1274    fn test_override_skip_macro_invocations() {
1275        let mut config = Config::default();
1276        config.override_value("skip_macro_invocations", r#"["*", "println"]"#);
1277        assert_eq!(
1278            config.skip_macro_invocations(),
1279            MacroSelectors(vec![
1280                MacroSelector::All,
1281                MacroSelector::Name(MacroName::new("println".to_owned()))
1282            ])
1283        );
1284    }
1285}