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 crate::config_option_with_style_edition_default;
520        use rustfmt_config_proc_macro::config_type;
521
522        #[config_type]
523        pub(crate) enum PartiallyUnstableOption {
524            V1,
525            V2,
526            #[unstable_variant]
527            V3,
528        }
529
530        config_option_with_style_edition_default!(
531            StableOption, bool, _ => false;
532            UnstableOption, bool, _ => false;
533            PartiallyUnstable, PartiallyUnstableOption, _ => PartiallyUnstableOption::V1;
534        );
535
536        create_config! {
537            // Options that are used by the generated functions
538            max_width: MaxWidth, true, "Maximum width of each line";
539            required_version: RequiredVersion, false, "Require a specific version of rustfmt.";
540            ignore: Ignore, false, "Skip formatting the specified files and directories.";
541            verbose: Verbose, false, "How much to information to emit to the user";
542            file_lines: FileLinesConfig, false,
543                "Lines to format; this is not supported in rustfmt.toml, and can only be specified \
544                    via the --file-lines option";
545
546            // merge_imports deprecation
547            imports_granularity: ImportsGranularityConfig, false, "Merge imports";
548            merge_imports: MergeImports, false, "(deprecated: use imports_granularity instead)";
549
550            // fn_args_layout renamed to fn_params_layout
551            fn_args_layout: FnArgsLayout, true, "(deprecated: use fn_params_layout instead)";
552            fn_params_layout: FnParamsLayout, true,
553                "Control the layout of parameters in a function signatures.";
554
555            // hide_parse_errors renamed to show_parse_errors
556            hide_parse_errors: HideParseErrors, false,
557                "(deprecated: use show_parse_errors instead)";
558            show_parse_errors: ShowParseErrors, false,
559                "Show errors from the parser (unstable)";
560
561
562            // Width Heuristics
563            use_small_heuristics: UseSmallHeuristics, true,
564                "Whether to use different formatting for items and \
565                 expressions if they satisfy a heuristic notion of 'small'.";
566            width_heuristics: WidthHeuristicsConfig, false, "'small' heuristic values";
567
568            fn_call_width: FnCallWidth, true, "Maximum width of the args of a function call before \
569                falling back to vertical formatting.";
570            attr_fn_like_width: AttrFnLikeWidth, true, "Maximum width of the args of a \
571                function-like attributes before falling back to vertical formatting.";
572            struct_lit_width: StructLitWidth, true, "Maximum width in the body of a struct lit \
573                before falling back to vertical formatting.";
574            struct_variant_width: StructVariantWidth, true, "Maximum width in the body of a struct \
575                variant before falling back to vertical formatting.";
576            array_width: ArrayWidth, true,  "Maximum width of an array literal before falling \
577                back to vertical formatting.";
578            chain_width: ChainWidth, true, "Maximum length of a chain to fit on a single line.";
579            single_line_if_else_max_width: SingleLineIfElseMaxWidth, true, "Maximum line length \
580                for single line if-else expressions. A value of zero means always break if-else \
581                expressions.";
582            single_line_let_else_max_width: SingleLineLetElseMaxWidth, false, "Maximum line length \
583                for single line let-else statements. A value of zero means always format the \
584                divergent `else` block over multiple lines.";
585
586            // Options that are used by the tests
587            stable_option: StableOption, true, "A stable option";
588            unstable_option: UnstableOption, false, "An unstable option";
589            partially_unstable_option: PartiallyUnstable, true, "A partially unstable option";
590            edition: EditionConfig, true, "blah";
591            style_edition: StyleEditionConfig, true, "blah";
592            version: VersionConfig, false, "blah blah"
593        }
594
595        #[cfg(test)]
596        mod partially_unstable_option {
597            use super::{Config, PartialConfig, PartiallyUnstableOption};
598            use rustfmt_config_proc_macro::{nightly_only_test, stable_only_test};
599            use std::path::Path;
600
601            /// From the config file, we can fill with a stable variant
602            #[test]
603            fn test_from_toml_stable_value() {
604                let toml = r#"
605                    partially_unstable_option = "V2"
606                "#;
607                let partial_config: PartialConfig = toml::from_str(toml).unwrap();
608                let config = Config::default();
609                let config = config.fill_from_parsed_config(partial_config, Path::new(""));
610                assert_eq!(
611                    config.partially_unstable_option(),
612                    PartiallyUnstableOption::V2
613                );
614            }
615
616            /// From the config file, we cannot fill with an unstable variant (stable only)
617            #[stable_only_test]
618            #[test]
619            fn test_from_toml_unstable_value_on_stable() {
620                let toml = r#"
621                    partially_unstable_option = "V3"
622                "#;
623                let partial_config: PartialConfig = toml::from_str(toml).unwrap();
624                let config = Config::default();
625                let config = config.fill_from_parsed_config(partial_config, Path::new(""));
626                assert_eq!(
627                    config.partially_unstable_option(),
628                    // default value from config, i.e. fill failed
629                    PartiallyUnstableOption::V1
630                );
631            }
632
633            /// From the config file, we can fill with an unstable variant (nightly only)
634            #[nightly_only_test]
635            #[test]
636            fn test_from_toml_unstable_value_on_nightly() {
637                let toml = r#"
638                    partially_unstable_option = "V3"
639                "#;
640                let partial_config: PartialConfig = toml::from_str(toml).unwrap();
641                let config = Config::default();
642                let config = config.fill_from_parsed_config(partial_config, Path::new(""));
643                assert_eq!(
644                    config.partially_unstable_option(),
645                    PartiallyUnstableOption::V3
646                );
647            }
648        }
649    }
650
651    #[test]
652    fn test_config_set() {
653        let mut config = Config::default();
654        config.set().verbose(Verbosity::Quiet);
655        assert_eq!(config.verbose(), Verbosity::Quiet);
656        config.set().verbose(Verbosity::Normal);
657        assert_eq!(config.verbose(), Verbosity::Normal);
658    }
659
660    #[test]
661    fn test_config_used_to_toml() {
662        let config = Config::default();
663
664        let merge_derives = config.merge_derives();
665        let skip_children = config.skip_children();
666
667        let used_options = config.used_options();
668        let toml = used_options.to_toml().unwrap();
669        assert_eq!(
670            toml,
671            format!("merge_derives = {merge_derives}\nskip_children = {skip_children}\n",)
672        );
673    }
674
675    #[test]
676    fn test_was_set() {
677        let config = Config::from_toml("hard_tabs = true", Path::new("./rustfmt.toml")).unwrap();
678
679        assert_eq!(config.was_set().hard_tabs(), true);
680        assert_eq!(config.was_set().verbose(), false);
681    }
682
683    const PRINT_DOCS_STABLE_OPTION: &str = "stable_option <boolean> Default: false";
684    const PRINT_DOCS_UNSTABLE_OPTION: &str = "unstable_option <boolean> Default: false (unstable)";
685    const PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION: &str =
686        "partially_unstable_option [V1|V2|V3 (unstable)] Default: V1";
687
688    #[test]
689    fn test_print_docs_exclude_unstable() {
690        use self::mock::Config;
691
692        let mut output = Vec::new();
693        Config::print_docs(&mut output, false);
694
695        let s = str::from_utf8(&output).unwrap();
696        assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true);
697        assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), false);
698        assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
699    }
700
701    #[test]
702    fn test_print_docs_include_unstable() {
703        use self::mock::Config;
704
705        let mut output = Vec::new();
706        Config::print_docs(&mut output, true);
707
708        let s = str::from_utf8(&output).unwrap();
709        assert_eq!(s.contains(PRINT_DOCS_STABLE_OPTION), true);
710        assert_eq!(s.contains(PRINT_DOCS_UNSTABLE_OPTION), true);
711        assert_eq!(s.contains(PRINT_DOCS_PARTIALLY_UNSTABLE_OPTION), true);
712    }
713
714    #[test]
715    fn test_dump_default_config() {
716        let default_config = format!(
717            r#"max_width = 100
718hard_tabs = false
719tab_spaces = 4
720newline_style = "Auto"
721indent_style = "Block"
722use_small_heuristics = "Default"
723fn_call_width = 60
724attr_fn_like_width = 70
725struct_lit_width = 18
726struct_variant_width = 35
727array_width = 60
728chain_width = 60
729single_line_if_else_max_width = 50
730single_line_let_else_max_width = 50
731wrap_comments = false
732format_code_in_doc_comments = false
733doc_comment_code_block_width = 100
734comment_width = 80
735normalize_comments = false
736normalize_doc_attributes = false
737format_strings = false
738format_macro_matchers = false
739format_macro_bodies = true
740skip_macro_invocations = []
741hex_literal_case = "Preserve"
742empty_item_single_line = true
743struct_lit_single_line = true
744fn_single_line = false
745where_single_line = false
746imports_indent = "Block"
747imports_layout = "Mixed"
748imports_granularity = "Preserve"
749group_imports = "Preserve"
750reorder_imports = true
751reorder_modules = true
752reorder_impl_items = false
753type_punctuation_density = "Wide"
754space_before_colon = false
755space_after_colon = true
756spaces_around_ranges = false
757binop_separator = "Front"
758remove_nested_parens = true
759combine_control_expr = true
760short_array_element_width_threshold = 10
761overflow_delimited_expr = false
762struct_field_align_threshold = 0
763enum_discrim_align_threshold = 0
764match_arm_blocks = true
765match_arm_leading_pipes = "Never"
766force_multiline_blocks = false
767fn_params_layout = "Tall"
768brace_style = "SameLineWhere"
769control_brace_style = "AlwaysSameLine"
770trailing_semicolon = true
771trailing_comma = "Vertical"
772match_block_trailing_comma = false
773blank_lines_upper_bound = 1
774blank_lines_lower_bound = 0
775edition = "2015"
776style_edition = "2015"
777version = "One"
778inline_attribute_width = 0
779format_generated_files = true
780generated_marker_line_search_limit = 5
781merge_derives = true
782use_try_shorthand = false
783use_field_init_shorthand = false
784force_explicit_abi = true
785condense_wildcard_suffixes = false
786color = "Auto"
787required_version = "{}"
788unstable_features = false
789disable_all_formatting = false
790skip_children = false
791show_parse_errors = true
792error_on_line_overflow = false
793error_on_unformatted = false
794ignore = []
795emit_mode = "Files"
796make_backup = false
797"#,
798            env!("CARGO_PKG_VERSION")
799        );
800        let toml = Config::default().all_options().to_toml().unwrap();
801        assert_eq!(&toml, &default_config);
802    }
803
804    #[test]
805    fn test_dump_style_edition_2024_config() {
806        let edition_2024_config = format!(
807            r#"max_width = 100
808hard_tabs = false
809tab_spaces = 4
810newline_style = "Auto"
811indent_style = "Block"
812use_small_heuristics = "Default"
813fn_call_width = 60
814attr_fn_like_width = 70
815struct_lit_width = 18
816struct_variant_width = 35
817array_width = 60
818chain_width = 60
819single_line_if_else_max_width = 50
820single_line_let_else_max_width = 50
821wrap_comments = false
822format_code_in_doc_comments = false
823doc_comment_code_block_width = 100
824comment_width = 80
825normalize_comments = false
826normalize_doc_attributes = false
827format_strings = false
828format_macro_matchers = false
829format_macro_bodies = true
830skip_macro_invocations = []
831hex_literal_case = "Preserve"
832empty_item_single_line = true
833struct_lit_single_line = true
834fn_single_line = false
835where_single_line = false
836imports_indent = "Block"
837imports_layout = "Mixed"
838imports_granularity = "Preserve"
839group_imports = "Preserve"
840reorder_imports = true
841reorder_modules = true
842reorder_impl_items = false
843type_punctuation_density = "Wide"
844space_before_colon = false
845space_after_colon = true
846spaces_around_ranges = false
847binop_separator = "Front"
848remove_nested_parens = true
849combine_control_expr = true
850short_array_element_width_threshold = 10
851overflow_delimited_expr = false
852struct_field_align_threshold = 0
853enum_discrim_align_threshold = 0
854match_arm_blocks = true
855match_arm_leading_pipes = "Never"
856force_multiline_blocks = false
857fn_params_layout = "Tall"
858brace_style = "SameLineWhere"
859control_brace_style = "AlwaysSameLine"
860trailing_semicolon = true
861trailing_comma = "Vertical"
862match_block_trailing_comma = false
863blank_lines_upper_bound = 1
864blank_lines_lower_bound = 0
865edition = "2015"
866style_edition = "2024"
867version = "Two"
868inline_attribute_width = 0
869format_generated_files = true
870generated_marker_line_search_limit = 5
871merge_derives = true
872use_try_shorthand = false
873use_field_init_shorthand = false
874force_explicit_abi = true
875condense_wildcard_suffixes = false
876color = "Auto"
877required_version = "{}"
878unstable_features = false
879disable_all_formatting = false
880skip_children = false
881show_parse_errors = true
882error_on_line_overflow = false
883error_on_unformatted = false
884ignore = []
885emit_mode = "Files"
886make_backup = false
887"#,
888            env!("CARGO_PKG_VERSION")
889        );
890        let toml = Config::default_with_style_edition(StyleEdition::Edition2024)
891            .all_options()
892            .to_toml()
893            .unwrap();
894        assert_eq!(&toml, &edition_2024_config);
895    }
896
897    #[test]
898    fn test_editions_2015_2018_2021_identical() {
899        let get_edition_toml = |style_edition: StyleEdition| {
900            Config::default_with_style_edition(style_edition)
901                .all_options()
902                .to_toml()
903                .unwrap()
904        };
905        let edition2015 = get_edition_toml(StyleEdition::Edition2015);
906        let edition2018 = get_edition_toml(StyleEdition::Edition2018);
907        let edition2021 = get_edition_toml(StyleEdition::Edition2021);
908        assert_eq!(edition2015, edition2018);
909        assert_eq!(edition2018, edition2021);
910    }
911
912    #[stable_only_test]
913    #[test]
914    fn test_as_not_nightly_channel() {
915        let mut config = Config::default();
916        assert_eq!(config.was_set().unstable_features(), false);
917        config.set().unstable_features(true);
918        assert_eq!(config.was_set().unstable_features(), false);
919    }
920
921    #[nightly_only_test]
922    #[test]
923    fn test_as_nightly_channel() {
924        let mut config = Config::default();
925        config.set().unstable_features(true);
926        // When we don't set the config from toml or command line options it
927        // doesn't get marked as set by the user.
928        assert_eq!(config.was_set().unstable_features(), false);
929        config.set().unstable_features(true);
930        assert_eq!(config.unstable_features(), true);
931    }
932
933    #[nightly_only_test]
934    #[test]
935    fn test_unstable_from_toml() {
936        let config =
937            Config::from_toml("unstable_features = true", Path::new("./rustfmt.toml")).unwrap();
938        assert_eq!(config.was_set().unstable_features(), true);
939        assert_eq!(config.unstable_features(), true);
940    }
941
942    #[test]
943    fn test_set_cli() {
944        let mut config = Config::default();
945        assert_eq!(config.was_set().edition(), false);
946        assert_eq!(config.was_set_cli().edition(), false);
947        config.set().edition(Edition::Edition2021);
948        assert_eq!(config.was_set().edition(), false);
949        assert_eq!(config.was_set_cli().edition(), false);
950        config.set_cli().edition(Edition::Edition2021);
951        assert_eq!(config.was_set().edition(), false);
952        assert_eq!(config.was_set_cli().edition(), true);
953        assert_eq!(config.was_set_cli().emit_mode(), false);
954    }
955
956    #[cfg(test)]
957    mod deprecated_option_merge_imports {
958        use super::*;
959
960        #[nightly_only_test]
961        #[test]
962        fn test_old_option_set() {
963            let toml = r#"
964                unstable_features = true
965                merge_imports = true
966            "#;
967            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
968            assert_eq!(config.imports_granularity(), ImportGranularity::Crate);
969        }
970
971        #[nightly_only_test]
972        #[test]
973        fn test_both_set() {
974            let toml = r#"
975                unstable_features = true
976                merge_imports = true
977                imports_granularity = "Preserve"
978            "#;
979            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
980            assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
981        }
982
983        #[nightly_only_test]
984        #[test]
985        fn test_new_overridden() {
986            let toml = r#"
987                unstable_features = true
988                merge_imports = true
989            "#;
990            let mut config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
991            config.override_value("imports_granularity", "Preserve");
992            assert_eq!(config.imports_granularity(), ImportGranularity::Preserve);
993        }
994
995        #[nightly_only_test]
996        #[test]
997        fn test_old_overridden() {
998            let toml = r#"
999                unstable_features = true
1000                imports_granularity = "Module"
1001            "#;
1002            let mut config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1003            config.override_value("merge_imports", "true");
1004            // no effect: the new option always takes precedence
1005            assert_eq!(config.imports_granularity(), ImportGranularity::Module);
1006        }
1007    }
1008
1009    #[cfg(test)]
1010    mod use_small_heuristics {
1011        use super::*;
1012
1013        #[test]
1014        fn test_default_sets_correct_widths() {
1015            let toml = r#"
1016                use_small_heuristics = "Default"
1017                max_width = 200
1018            "#;
1019            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1020            assert_eq!(config.array_width(), 120);
1021            assert_eq!(config.attr_fn_like_width(), 140);
1022            assert_eq!(config.chain_width(), 120);
1023            assert_eq!(config.fn_call_width(), 120);
1024            assert_eq!(config.single_line_if_else_max_width(), 100);
1025            assert_eq!(config.struct_lit_width(), 36);
1026            assert_eq!(config.struct_variant_width(), 70);
1027        }
1028
1029        #[test]
1030        fn test_max_sets_correct_widths() {
1031            let toml = r#"
1032                use_small_heuristics = "Max"
1033                max_width = 120
1034            "#;
1035            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1036            assert_eq!(config.array_width(), 120);
1037            assert_eq!(config.attr_fn_like_width(), 120);
1038            assert_eq!(config.chain_width(), 120);
1039            assert_eq!(config.fn_call_width(), 120);
1040            assert_eq!(config.single_line_if_else_max_width(), 120);
1041            assert_eq!(config.struct_lit_width(), 120);
1042            assert_eq!(config.struct_variant_width(), 120);
1043        }
1044
1045        #[test]
1046        fn test_off_sets_correct_widths() {
1047            let toml = r#"
1048                use_small_heuristics = "Off"
1049                max_width = 100
1050            "#;
1051            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1052            assert_eq!(config.array_width(), usize::MAX);
1053            assert_eq!(config.attr_fn_like_width(), usize::MAX);
1054            assert_eq!(config.chain_width(), usize::MAX);
1055            assert_eq!(config.fn_call_width(), usize::MAX);
1056            assert_eq!(config.single_line_if_else_max_width(), 0);
1057            assert_eq!(config.struct_lit_width(), 0);
1058            assert_eq!(config.struct_variant_width(), 0);
1059        }
1060
1061        #[test]
1062        fn test_override_works_with_default() {
1063            let toml = r#"
1064                use_small_heuristics = "Default"
1065                array_width = 20
1066                attr_fn_like_width = 40
1067                chain_width = 20
1068                fn_call_width = 90
1069                single_line_if_else_max_width = 40
1070                struct_lit_width = 30
1071                struct_variant_width = 34
1072            "#;
1073            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1074            assert_eq!(config.array_width(), 20);
1075            assert_eq!(config.attr_fn_like_width(), 40);
1076            assert_eq!(config.chain_width(), 20);
1077            assert_eq!(config.fn_call_width(), 90);
1078            assert_eq!(config.single_line_if_else_max_width(), 40);
1079            assert_eq!(config.struct_lit_width(), 30);
1080            assert_eq!(config.struct_variant_width(), 34);
1081        }
1082
1083        #[test]
1084        fn test_override_with_max() {
1085            let toml = r#"
1086                use_small_heuristics = "Max"
1087                array_width = 20
1088                attr_fn_like_width = 40
1089                chain_width = 20
1090                fn_call_width = 90
1091                single_line_if_else_max_width = 40
1092                struct_lit_width = 30
1093                struct_variant_width = 34
1094            "#;
1095            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1096            assert_eq!(config.array_width(), 20);
1097            assert_eq!(config.attr_fn_like_width(), 40);
1098            assert_eq!(config.chain_width(), 20);
1099            assert_eq!(config.fn_call_width(), 90);
1100            assert_eq!(config.single_line_if_else_max_width(), 40);
1101            assert_eq!(config.struct_lit_width(), 30);
1102            assert_eq!(config.struct_variant_width(), 34);
1103        }
1104
1105        #[test]
1106        fn test_override_with_off() {
1107            let toml = r#"
1108                use_small_heuristics = "Off"
1109                array_width = 20
1110                attr_fn_like_width = 40
1111                chain_width = 20
1112                fn_call_width = 90
1113                single_line_if_else_max_width = 40
1114                struct_lit_width = 30
1115                struct_variant_width = 34
1116            "#;
1117            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1118            assert_eq!(config.array_width(), 20);
1119            assert_eq!(config.attr_fn_like_width(), 40);
1120            assert_eq!(config.chain_width(), 20);
1121            assert_eq!(config.fn_call_width(), 90);
1122            assert_eq!(config.single_line_if_else_max_width(), 40);
1123            assert_eq!(config.struct_lit_width(), 30);
1124            assert_eq!(config.struct_variant_width(), 34);
1125        }
1126
1127        #[test]
1128        fn test_fn_call_width_config_exceeds_max_width() {
1129            let toml = r#"
1130                max_width = 90
1131                fn_call_width = 95
1132            "#;
1133            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1134            assert_eq!(config.fn_call_width(), 90);
1135        }
1136
1137        #[test]
1138        fn test_attr_fn_like_width_config_exceeds_max_width() {
1139            let toml = r#"
1140                max_width = 80
1141                attr_fn_like_width = 90
1142            "#;
1143            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1144            assert_eq!(config.attr_fn_like_width(), 80);
1145        }
1146
1147        #[test]
1148        fn test_struct_lit_config_exceeds_max_width() {
1149            let toml = r#"
1150                max_width = 78
1151                struct_lit_width = 90
1152            "#;
1153            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1154            assert_eq!(config.struct_lit_width(), 78);
1155        }
1156
1157        #[test]
1158        fn test_struct_variant_width_config_exceeds_max_width() {
1159            let toml = r#"
1160                max_width = 80
1161                struct_variant_width = 90
1162            "#;
1163            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1164            assert_eq!(config.struct_variant_width(), 80);
1165        }
1166
1167        #[test]
1168        fn test_array_width_config_exceeds_max_width() {
1169            let toml = r#"
1170                max_width = 60
1171                array_width = 80
1172            "#;
1173            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1174            assert_eq!(config.array_width(), 60);
1175        }
1176
1177        #[test]
1178        fn test_chain_width_config_exceeds_max_width() {
1179            let toml = r#"
1180                max_width = 80
1181                chain_width = 90
1182            "#;
1183            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1184            assert_eq!(config.chain_width(), 80);
1185        }
1186
1187        #[test]
1188        fn test_single_line_if_else_max_width_config_exceeds_max_width() {
1189            let toml = r#"
1190                max_width = 70
1191                single_line_if_else_max_width = 90
1192            "#;
1193            let config = Config::from_toml(toml, Path::new("./rustfmt.toml")).unwrap();
1194            assert_eq!(config.single_line_if_else_max_width(), 70);
1195        }
1196
1197        #[test]
1198        fn test_override_fn_call_width_exceeds_max_width() {
1199            let mut config = Config::default();
1200            config.override_value("fn_call_width", "101");
1201            assert_eq!(config.fn_call_width(), 100);
1202        }
1203
1204        #[test]
1205        fn test_override_attr_fn_like_width_exceeds_max_width() {
1206            let mut config = Config::default();
1207            config.override_value("attr_fn_like_width", "101");
1208            assert_eq!(config.attr_fn_like_width(), 100);
1209        }
1210
1211        #[test]
1212        fn test_override_struct_lit_exceeds_max_width() {
1213            let mut config = Config::default();
1214            config.override_value("struct_lit_width", "101");
1215            assert_eq!(config.struct_lit_width(), 100);
1216        }
1217
1218        #[test]
1219        fn test_override_struct_variant_width_exceeds_max_width() {
1220            let mut config = Config::default();
1221            config.override_value("struct_variant_width", "101");
1222            assert_eq!(config.struct_variant_width(), 100);
1223        }
1224
1225        #[test]
1226        fn test_override_array_width_exceeds_max_width() {
1227            let mut config = Config::default();
1228            config.override_value("array_width", "101");
1229            assert_eq!(config.array_width(), 100);
1230        }
1231
1232        #[test]
1233        fn test_override_chain_width_exceeds_max_width() {
1234            let mut config = Config::default();
1235            config.override_value("chain_width", "101");
1236            assert_eq!(config.chain_width(), 100);
1237        }
1238
1239        #[test]
1240        fn test_override_single_line_if_else_max_width_exceeds_max_width() {
1241            let mut config = Config::default();
1242            config.override_value("single_line_if_else_max_width", "101");
1243            assert_eq!(config.single_line_if_else_max_width(), 100);
1244        }
1245    }
1246
1247    #[cfg(test)]
1248    mod partially_unstable_option {
1249        use super::mock::{Config, PartiallyUnstableOption};
1250
1251        /// From the command line, we can override with a stable variant.
1252        #[test]
1253        fn test_override_stable_value() {
1254            let mut config = Config::default();
1255            config.override_value("partially_unstable_option", "V2");
1256            assert_eq!(
1257                config.partially_unstable_option(),
1258                PartiallyUnstableOption::V2
1259            );
1260        }
1261
1262        /// From the command line, we can override with an unstable variant.
1263        #[test]
1264        fn test_override_unstable_value() {
1265            let mut config = Config::default();
1266            config.override_value("partially_unstable_option", "V3");
1267            assert_eq!(
1268                config.partially_unstable_option(),
1269                PartiallyUnstableOption::V3
1270            );
1271        }
1272    }
1273
1274    #[test]
1275    fn test_override_skip_macro_invocations() {
1276        let mut config = Config::default();
1277        config.override_value("skip_macro_invocations", r#"["*", "println"]"#);
1278        assert_eq!(
1279            config.skip_macro_invocations(),
1280            MacroSelectors(vec![
1281                MacroSelector::All,
1282                MacroSelector::Name(MacroName::new("println".to_owned()))
1283            ])
1284        );
1285    }
1286}