diff options
| author | Caleb Cartwright <calebcartwright@users.noreply.github.com> | 2021-10-19 23:01:25 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-10-19 23:01:25 -0500 |
| commit | 923126348d012e8a81527de84ccb1cb1d9fde75c (patch) | |
| tree | 83cad4971c9540befdca393dd27fb14edb4a1952 /src | |
| parent | 9060bc5583f80a37530ee35a6cc2ddf1293d068f (diff) | |
| parent | 1ae5c35f8d2a0d2ce5d914a479839dc0f3eb70f9 (diff) | |
| download | rust-923126348d012e8a81527de84ccb1cb1d9fde75c.tar.gz rust-923126348d012e8a81527de84ccb1cb1d9fde75c.zip | |
Merge branch 'master' into 1.4.38-subtree
Diffstat (limited to 'src')
| -rw-r--r-- | src/attr.rs | 5 | ||||
| -rw-r--r-- | src/bin/main.rs | 2 | ||||
| -rw-r--r-- | src/cargo-fmt/main.rs | 293 | ||||
| -rw-r--r-- | src/cargo-fmt/test/message_format.rs | 80 | ||||
| -rw-r--r-- | src/cargo-fmt/test/mod.rs | 137 | ||||
| -rw-r--r-- | src/cargo-fmt/test/targets.rs | 134 | ||||
| -rw-r--r-- | src/comment.rs | 68 | ||||
| -rw-r--r-- | src/config/mod.rs | 9 | ||||
| -rw-r--r-- | src/config/options.rs | 13 | ||||
| -rw-r--r-- | src/expr.rs | 46 | ||||
| -rw-r--r-- | src/formatting.rs | 9 | ||||
| -rw-r--r-- | src/formatting/generated.rs | 7 | ||||
| -rw-r--r-- | src/imports.rs | 146 | ||||
| -rw-r--r-- | src/items.rs | 146 | ||||
| -rw-r--r-- | src/lists.rs | 8 | ||||
| -rw-r--r-- | src/matches.rs | 1 | ||||
| -rw-r--r-- | src/missed_spans.rs | 20 | ||||
| -rw-r--r-- | src/overflow.rs | 4 | ||||
| -rw-r--r-- | src/patterns.rs | 24 | ||||
| -rw-r--r-- | src/reorder.rs | 1 | ||||
| -rw-r--r-- | src/spanned.rs | 2 | ||||
| -rw-r--r-- | src/syntux/session.rs | 6 | ||||
| -rw-r--r-- | src/test/mod.rs | 7 | ||||
| -rw-r--r-- | src/types.rs | 135 | ||||
| -rw-r--r-- | src/visitor.rs | 63 |
25 files changed, 848 insertions, 518 deletions
diff --git a/src/attr.rs b/src/attr.rs index 315eb10a9db..a5982820e3d 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -13,6 +13,7 @@ use crate::lists::{definitive_tactic, itemize_list, write_list, ListFormatting, use crate::overflow; use crate::rewrite::{Rewrite, RewriteContext}; use crate::shape::Shape; +use crate::source_map::SpanUtils; use crate::types::{rewrite_path, PathContext}; use crate::utils::{count_newlines, mk_sp}; @@ -116,7 +117,9 @@ fn format_derive( |span| span.lo(), |span| span.hi(), |span| Some(context.snippet(*span).to_owned()), - attr.span.lo(), + // We update derive attribute spans to start after the opening '(' + // This helps us focus parsing to just what's inside #[derive(...)] + context.snippet_provider.span_after(attr.span, "("), attr.span.hi(), false, ); diff --git a/src/bin/main.rs b/src/bin/main.rs index 4b4aa42d935..1bcc5c0dada 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -121,7 +121,7 @@ fn make_opts() -> Options { found reverts to the input file path", "[Path for the configuration file]", ); - opts.optopt("", "edition", "Rust edition to use", "[2015|2018]"); + opts.optopt("", "edition", "Rust edition to use", "[2015|2018|2021]"); opts.optopt( "", "color", diff --git a/src/cargo-fmt/main.rs b/src/cargo-fmt/main.rs index 90ffad927e2..1d423ac3491 100644 --- a/src/cargo-fmt/main.rs +++ b/src/cargo-fmt/main.rs @@ -17,6 +17,10 @@ use std::str; use structopt::StructOpt; +#[path = "test/mod.rs"] +#[cfg(test)] +mod cargo_fmt_tests; + #[derive(StructOpt, Debug)] #[structopt( bin_name = "cargo fmt", @@ -36,7 +40,7 @@ pub struct Opts { #[structopt(long = "version")] version: bool, - /// Specify package to format (only usable in workspaces) + /// Specify package to format #[structopt(short = "p", long = "package", value_name = "package")] packages: Vec<String>, @@ -53,9 +57,13 @@ pub struct Opts { #[structopt(name = "rustfmt_options", raw(true))] rustfmt_options: Vec<String>, - /// Format all packages (only usable in workspaces) + /// Format all packages, and also their local path-based dependencies #[structopt(long = "all")] format_all: bool, + + /// Run rustfmt in check mode + #[structopt(long = "check")] + check: bool, } fn main() { @@ -104,6 +112,12 @@ fn execute() -> i32 { let strategy = CargoFmtStrategy::from_opts(&opts); let mut rustfmt_args = opts.rustfmt_options; + if opts.check { + let check_flag = "--check"; + if !rustfmt_args.iter().any(|o| o == check_flag) { + rustfmt_args.push(check_flag.to_owned()); + } + } if let Some(message_format) = opts.message_format { if let Err(msg) = convert_message_format_to_rustfmt_args(&message_format, &mut rustfmt_args) { @@ -346,7 +360,7 @@ fn get_targets_root_only( manifest_path: Option<&Path>, targets: &mut BTreeSet<Target>, ) -> Result<(), io::Error> { - let metadata = get_cargo_metadata(manifest_path, false)?; + let metadata = get_cargo_metadata(manifest_path)?; let workspace_root_path = PathBuf::from(&metadata.workspace_root).canonicalize()?; let (in_workspace_root, current_dir_manifest) = if let Some(target_manifest) = manifest_path { ( @@ -390,34 +404,29 @@ fn get_targets_recursive( mut targets: &mut BTreeSet<Target>, visited: &mut BTreeSet<String>, ) -> Result<(), io::Error> { - let metadata = get_cargo_metadata(manifest_path, false)?; - let metadata_with_deps = get_cargo_metadata(manifest_path, true)?; - - for package in metadata.packages { + let metadata = get_cargo_metadata(manifest_path)?; + for package in &metadata.packages { add_targets(&package.targets, &mut targets); - // Look for local dependencies. - for dependency in package.dependencies { - if dependency.source.is_some() || visited.contains(&dependency.name) { + // Look for local dependencies using information available since cargo v1.51 + // It's theoretically possible someone could use a newer version of rustfmt with + // a much older version of `cargo`, but we don't try to explicitly support that scenario. + // If someone reports an issue with path-based deps not being formatted, be sure to + // confirm their version of `cargo` (not `cargo-fmt`) is >= v1.51 + // https://github.com/rust-lang/cargo/pull/8994 + for dependency in &package.dependencies { + if dependency.path.is_none() || visited.contains(&dependency.name) { continue; } - let dependency_package = metadata_with_deps - .packages - .iter() - .find(|p| p.name == dependency.name && p.source.is_none()); - let manifest_path = if let Some(dep_pkg) = dependency_package { - PathBuf::from(&dep_pkg.manifest_path) - } else { - let mut package_manifest_path = PathBuf::from(&package.manifest_path); - package_manifest_path.pop(); - package_manifest_path.push(&dependency.name); - package_manifest_path.push("Cargo.toml"); - package_manifest_path - }; - - if manifest_path.exists() { - visited.insert(dependency.name); + let manifest_path = PathBuf::from(dependency.path.as_ref().unwrap()).join("Cargo.toml"); + if manifest_path.exists() + && !metadata + .packages + .iter() + .any(|p| p.manifest_path.eq(&manifest_path)) + { + visited.insert(dependency.name.to_owned()); get_targets_recursive(Some(&manifest_path), &mut targets, visited)?; } } @@ -431,8 +440,7 @@ fn get_targets_with_hitlist( hitlist: &[String], targets: &mut BTreeSet<Target>, ) -> Result<(), io::Error> { - let metadata = get_cargo_metadata(manifest_path, false)?; - + let metadata = get_cargo_metadata(manifest_path)?; let mut workspace_hitlist: BTreeSet<&String> = BTreeSet::from_iter(hitlist); for package in metadata.packages { @@ -517,18 +525,13 @@ fn run_rustfmt( .unwrap_or(SUCCESS)) } -fn get_cargo_metadata( - manifest_path: Option<&Path>, - include_deps: bool, -) -> Result<cargo_metadata::Metadata, io::Error> { +fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> { let mut cmd = cargo_metadata::MetadataCommand::new(); - if !include_deps { - cmd.no_deps(); - } + cmd.no_deps(); if let Some(manifest_path) = manifest_path { cmd.manifest_path(manifest_path); } - cmd.other_options(&[String::from("--offline")]); + cmd.other_options(vec![String::from("--offline")]); match cmd.exec() { Ok(metadata) => Ok(metadata), @@ -541,221 +544,3 @@ fn get_cargo_metadata( } } } - -#[cfg(test)] -mod cargo_fmt_tests { - use super::*; - - #[test] - fn default_options() { - let empty: Vec<String> = vec![]; - let o = Opts::from_iter(&empty); - assert_eq!(false, o.quiet); - assert_eq!(false, o.verbose); - assert_eq!(false, o.version); - assert_eq!(empty, o.packages); - assert_eq!(empty, o.rustfmt_options); - assert_eq!(false, o.format_all); - assert_eq!(None, o.manifest_path); - assert_eq!(None, o.message_format); - } - - #[test] - fn good_options() { - let o = Opts::from_iter(&[ - "test", - "-q", - "-p", - "p1", - "-p", - "p2", - "--message-format", - "short", - "--", - "--edition", - "2018", - ]); - assert_eq!(true, o.quiet); - assert_eq!(false, o.verbose); - assert_eq!(false, o.version); - assert_eq!(vec!["p1", "p2"], o.packages); - assert_eq!(vec!["--edition", "2018"], o.rustfmt_options); - assert_eq!(false, o.format_all); - assert_eq!(Some(String::from("short")), o.message_format); - } - - #[test] - fn unexpected_option() { - assert!( - Opts::clap() - .get_matches_from_safe(&["test", "unexpected"]) - .is_err() - ); - } - - #[test] - fn unexpected_flag() { - assert!( - Opts::clap() - .get_matches_from_safe(&["test", "--flag"]) - .is_err() - ); - } - - #[test] - fn mandatory_separator() { - assert!( - Opts::clap() - .get_matches_from_safe(&["test", "--check"]) - .is_err() - ); - assert!( - !Opts::clap() - .get_matches_from_safe(&["test", "--", "--check"]) - .is_err() - ); - } - - #[test] - fn multiple_packages_one_by_one() { - let o = Opts::from_iter(&[ - "test", - "-p", - "package1", - "--package", - "package2", - "-p", - "package3", - ]); - assert_eq!(3, o.packages.len()); - } - - #[test] - fn multiple_packages_grouped() { - let o = Opts::from_iter(&[ - "test", - "--package", - "package1", - "package2", - "-p", - "package3", - "package4", - ]); - assert_eq!(4, o.packages.len()); - } - - #[test] - fn empty_packages_1() { - assert!(Opts::clap().get_matches_from_safe(&["test", "-p"]).is_err()); - } - - #[test] - fn empty_packages_2() { - assert!( - Opts::clap() - .get_matches_from_safe(&["test", "-p", "--", "--check"]) - .is_err() - ); - } - - #[test] - fn empty_packages_3() { - assert!( - Opts::clap() - .get_matches_from_safe(&["test", "-p", "--verbose"]) - .is_err() - ); - } - - #[test] - fn empty_packages_4() { - assert!( - Opts::clap() - .get_matches_from_safe(&["test", "-p", "--check"]) - .is_err() - ); - } - - mod convert_message_format_to_rustfmt_args_tests { - use super::*; - - #[test] - fn invalid_message_format() { - assert_eq!( - convert_message_format_to_rustfmt_args("awesome", &mut vec![]), - Err(String::from( - "invalid --message-format value: awesome. Allowed values are: short|json|human" - )), - ); - } - - #[test] - fn json_message_format_and_check_arg() { - let mut args = vec![String::from("--check")]; - assert_eq!( - convert_message_format_to_rustfmt_args("json", &mut args), - Err(String::from( - "cannot include --check arg when --message-format is set to json" - )), - ); - } - - #[test] - fn json_message_format_and_emit_arg() { - let mut args = vec![String::from("--emit"), String::from("checkstyle")]; - assert_eq!( - convert_message_format_to_rustfmt_args("json", &mut args), - Err(String::from( - "cannot include --emit arg when --message-format is set to json" - )), - ); - } - - #[test] - fn json_message_format() { - let mut args = vec![String::from("--edition"), String::from("2018")]; - assert!(convert_message_format_to_rustfmt_args("json", &mut args).is_ok()); - assert_eq!( - args, - vec![ - String::from("--edition"), - String::from("2018"), - String::from("--emit"), - String::from("json") - ] - ); - } - - #[test] - fn human_message_format() { - let exp_args = vec![String::from("--emit"), String::from("json")]; - let mut act_args = exp_args.clone(); - assert!(convert_message_format_to_rustfmt_args("human", &mut act_args).is_ok()); - assert_eq!(act_args, exp_args); - } - - #[test] - fn short_message_format() { - let mut args = vec![String::from("--check")]; - assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); - assert_eq!(args, vec![String::from("--check"), String::from("-l")]); - } - - #[test] - fn short_message_format_included_short_list_files_flag() { - let mut args = vec![String::from("--check"), String::from("-l")]; - assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); - assert_eq!(args, vec![String::from("--check"), String::from("-l")]); - } - - #[test] - fn short_message_format_included_long_list_files_flag() { - let mut args = vec![String::from("--check"), String::from("--files-with-diff")]; - assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); - assert_eq!( - args, - vec![String::from("--check"), String::from("--files-with-diff")] - ); - } - } -} diff --git a/src/cargo-fmt/test/message_format.rs b/src/cargo-fmt/test/message_format.rs new file mode 100644 index 00000000000..bf44924f13c --- /dev/null +++ b/src/cargo-fmt/test/message_format.rs @@ -0,0 +1,80 @@ +use super::*; + +#[test] +fn invalid_message_format() { + assert_eq!( + convert_message_format_to_rustfmt_args("awesome", &mut vec![]), + Err(String::from( + "invalid --message-format value: awesome. Allowed values are: short|json|human" + )), + ); +} + +#[test] +fn json_message_format_and_check_arg() { + let mut args = vec![String::from("--check")]; + assert_eq!( + convert_message_format_to_rustfmt_args("json", &mut args), + Err(String::from( + "cannot include --check arg when --message-format is set to json" + )), + ); +} + +#[test] +fn json_message_format_and_emit_arg() { + let mut args = vec![String::from("--emit"), String::from("checkstyle")]; + assert_eq!( + convert_message_format_to_rustfmt_args("json", &mut args), + Err(String::from( + "cannot include --emit arg when --message-format is set to json" + )), + ); +} + +#[test] +fn json_message_format() { + let mut args = vec![String::from("--edition"), String::from("2018")]; + assert!(convert_message_format_to_rustfmt_args("json", &mut args).is_ok()); + assert_eq!( + args, + vec![ + String::from("--edition"), + String::from("2018"), + String::from("--emit"), + String::from("json") + ] + ); +} + +#[test] +fn human_message_format() { + let exp_args = vec![String::from("--emit"), String::from("json")]; + let mut act_args = exp_args.clone(); + assert!(convert_message_format_to_rustfmt_args("human", &mut act_args).is_ok()); + assert_eq!(act_args, exp_args); +} + +#[test] +fn short_message_format() { + let mut args = vec![String::from("--check")]; + assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); + assert_eq!(args, vec![String::from("--check"), String::from("-l")]); +} + +#[test] +fn short_message_format_included_short_list_files_flag() { + let mut args = vec![String::from("--check"), String::from("-l")]; + assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); + assert_eq!(args, vec![String::from("--check"), String::from("-l")]); +} + +#[test] +fn short_message_format_included_long_list_files_flag() { + let mut args = vec![String::from("--check"), String::from("--files-with-diff")]; + assert!(convert_message_format_to_rustfmt_args("short", &mut args).is_ok()); + assert_eq!( + args, + vec![String::from("--check"), String::from("--files-with-diff")] + ); +} diff --git a/src/cargo-fmt/test/mod.rs b/src/cargo-fmt/test/mod.rs new file mode 100644 index 00000000000..360503632c7 --- /dev/null +++ b/src/cargo-fmt/test/mod.rs @@ -0,0 +1,137 @@ +use super::*; + +mod message_format; +mod targets; + +#[test] +fn default_options() { + let empty: Vec<String> = vec![]; + let o = Opts::from_iter(&empty); + assert_eq!(false, o.quiet); + assert_eq!(false, o.verbose); + assert_eq!(false, o.version); + assert_eq!(false, o.check); + assert_eq!(empty, o.packages); + assert_eq!(empty, o.rustfmt_options); + assert_eq!(false, o.format_all); + assert_eq!(None, o.manifest_path); + assert_eq!(None, o.message_format); +} + +#[test] +fn good_options() { + let o = Opts::from_iter(&[ + "test", + "-q", + "-p", + "p1", + "-p", + "p2", + "--message-format", + "short", + "--check", + "--", + "--edition", + "2018", + ]); + assert_eq!(true, o.quiet); + assert_eq!(false, o.verbose); + assert_eq!(false, o.version); + assert_eq!(true, o.check); + assert_eq!(vec!["p1", "p2"], o.packages); + assert_eq!(vec!["--edition", "2018"], o.rustfmt_options); + assert_eq!(false, o.format_all); + assert_eq!(Some(String::from("short")), o.message_format); +} + +#[test] +fn unexpected_option() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "unexpected"]) + .is_err() + ); +} + +#[test] +fn unexpected_flag() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "--flag"]) + .is_err() + ); +} + +#[test] +fn mandatory_separator() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "--emit"]) + .is_err() + ); + assert!( + !Opts::clap() + .get_matches_from_safe(&["test", "--", "--emit"]) + .is_err() + ); +} + +#[test] +fn multiple_packages_one_by_one() { + let o = Opts::from_iter(&[ + "test", + "-p", + "package1", + "--package", + "package2", + "-p", + "package3", + ]); + assert_eq!(3, o.packages.len()); +} + +#[test] +fn multiple_packages_grouped() { + let o = Opts::from_iter(&[ + "test", + "--package", + "package1", + "package2", + "-p", + "package3", + "package4", + ]); + assert_eq!(4, o.packages.len()); +} + +#[test] +fn empty_packages_1() { + assert!(Opts::clap().get_matches_from_safe(&["test", "-p"]).is_err()); +} + +#[test] +fn empty_packages_2() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "-p", "--", "--check"]) + .is_err() + ); +} + +#[test] +fn empty_packages_3() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "-p", "--verbose"]) + .is_err() + ); +} + +#[test] +fn empty_packages_4() { + assert!( + Opts::clap() + .get_matches_from_safe(&["test", "-p", "--check"]) + .is_err() + ); +} diff --git a/src/cargo-fmt/test/targets.rs b/src/cargo-fmt/test/targets.rs new file mode 100644 index 00000000000..b7e7fabdf71 --- /dev/null +++ b/src/cargo-fmt/test/targets.rs @@ -0,0 +1,134 @@ +use super::*; + +struct ExpTarget { + path: &'static str, + edition: &'static str, + kind: &'static str, +} + +mod all_targets { + use super::*; + + fn assert_correct_targets_loaded( + manifest_suffix: &str, + source_root: &str, + exp_targets: &[ExpTarget], + exp_num_targets: usize, + ) { + let root_path = Path::new("tests/cargo-fmt/source").join(source_root); + let get_path = |exp: &str| PathBuf::from(&root_path).join(exp).canonicalize().unwrap(); + let manifest_path = Path::new(&root_path).join(manifest_suffix); + let targets = get_targets(&CargoFmtStrategy::All, Some(manifest_path.as_path())) + .expect("Targets should have been loaded"); + + assert_eq!(targets.len(), exp_num_targets); + + for target in exp_targets { + assert!(targets.contains(&Target { + path: get_path(target.path), + edition: target.edition.to_owned(), + kind: target.kind.to_owned(), + })); + } + } + + mod different_crate_and_dir_names { + use super::*; + + fn assert_correct_targets_loaded(manifest_suffix: &str) { + let exp_targets = vec![ + ExpTarget { + path: "dependency-dir-name/subdep-dir-name/src/lib.rs", + edition: "2018", + kind: "lib", + }, + ExpTarget { + path: "dependency-dir-name/src/lib.rs", + edition: "2018", + kind: "lib", + }, + ExpTarget { + path: "src/main.rs", + edition: "2018", + kind: "main", + }, + ]; + super::assert_correct_targets_loaded( + manifest_suffix, + "divergent-crate-dir-names", + &exp_targets, + 3, + ); + } + + #[test] + fn correct_targets_from_root() { + assert_correct_targets_loaded("Cargo.toml"); + } + + #[test] + fn correct_targets_from_sub_local_dep() { + assert_correct_targets_loaded("dependency-dir-name/Cargo.toml"); + } + } + + mod workspaces { + use super::*; + + fn assert_correct_targets_loaded(manifest_suffix: &str) { + let exp_targets = vec![ + ExpTarget { + path: "ws/a/src/main.rs", + edition: "2018", + kind: "bin", + }, + ExpTarget { + path: "ws/b/src/main.rs", + edition: "2018", + kind: "bin", + }, + ExpTarget { + path: "ws/c/src/lib.rs", + edition: "2018", + kind: "lib", + }, + ExpTarget { + path: "ws/a/d/src/lib.rs", + edition: "2018", + kind: "lib", + }, + ExpTarget { + path: "e/src/main.rs", + edition: "2018", + kind: "main", + }, + ExpTarget { + path: "ws/a/d/f/src/lib.rs", + edition: "2018", + kind: "lib", + }, + ]; + super::assert_correct_targets_loaded( + manifest_suffix, + "workspaces/path-dep-above", + &exp_targets, + 6, + ); + } + + #[test] + fn includes_outside_workspace_deps() { + assert_correct_targets_loaded("ws/Cargo.toml"); + } + + #[test] + fn includes_workspace_from_dep_above() { + assert_correct_targets_loaded("e/Cargo.toml"); + } + + #[test] + fn includes_all_packages_from_workspace_subdir() { + assert_correct_targets_loaded("ws/a/d/f/Cargo.toml"); + } + } +} diff --git a/src/comment.rs b/src/comment.rs index 58a2b5e6aec..60825424858 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -10,7 +10,8 @@ use crate::rewrite::RewriteContext; use crate::shape::{Indent, Shape}; use crate::string::{rewrite_string, StringFormat}; use crate::utils::{ - count_newlines, first_line_width, last_line_width, trim_left_preserve_layout, unicode_str_width, + count_newlines, first_line_width, last_line_width, trim_left_preserve_layout, + trimmed_last_line_width, unicode_str_width, }; use crate::{ErrorKind, FormattingError}; @@ -171,11 +172,12 @@ pub(crate) fn combine_strs_with_missing_comments( String::with_capacity(prev_str.len() + next_str.len() + shape.indent.width() + 128); result.push_str(prev_str); let mut allow_one_line = !prev_str.contains('\n') && !next_str.contains('\n'); - let first_sep = if prev_str.is_empty() || next_str.is_empty() { - "" - } else { - " " - }; + let first_sep = + if prev_str.is_empty() || next_str.is_empty() || trimmed_last_line_width(prev_str) == 0 { + "" + } else { + " " + }; let mut one_line_width = last_line_width(prev_str) + first_line_width(next_str) + first_sep.len(); @@ -184,7 +186,7 @@ pub(crate) fn combine_strs_with_missing_comments( let missing_comment = rewrite_missing_comment(span, shape, context)?; if missing_comment.is_empty() { - if allow_extend && prev_str.len() + first_sep.len() + next_str.len() <= shape.width { + if allow_extend && one_line_width <= shape.width { result.push_str(first_sep); } else if !prev_str.is_empty() { result.push_str(&indent.to_string_with_newline(config)) @@ -392,28 +394,26 @@ fn identify_comment( } } -/// Attributes for code blocks in rustdoc. -/// See <https://doc.rust-lang.org/rustdoc/print.html#attributes>. +/// Enum indicating if the code block contains rust based on attributes enum CodeBlockAttribute { Rust, - Ignore, - Text, - ShouldPanic, - NoRun, - CompileFail, + NotRust, } impl CodeBlockAttribute { - fn new(attribute: &str) -> CodeBlockAttribute { - match attribute { - "rust" | "" => CodeBlockAttribute::Rust, - "ignore" => CodeBlockAttribute::Ignore, - "text" => CodeBlockAttribute::Text, - "should_panic" => CodeBlockAttribute::ShouldPanic, - "no_run" => CodeBlockAttribute::NoRun, - "compile_fail" => CodeBlockAttribute::CompileFail, - _ => CodeBlockAttribute::Text, + /// Parse comma separated attributes list. Return rust only if all + /// attributes are valid rust attributes + /// See https://doc.rust-lang.org/rustdoc/print.html#attributes + fn new(attributes: &str) -> CodeBlockAttribute { + for attribute in attributes.split(",") { + match attribute.trim() { + "" | "rust" | "should_panic" | "no_run" | "edition2015" | "edition2018" + | "edition2021" => (), + "ignore" | "compile_fail" | "text" => return CodeBlockAttribute::NotRust, + _ => return CodeBlockAttribute::NotRust, + } } + CodeBlockAttribute::Rust } } @@ -647,25 +647,21 @@ impl<'a> CommentRewrite<'a> { } else if self.code_block_attr.is_some() { if line.starts_with("```") { let code_block = match self.code_block_attr.as_ref().unwrap() { - CodeBlockAttribute::Ignore | CodeBlockAttribute::Text => { - trim_custom_comment_prefix(&self.code_block_buffer) - } - _ if self.code_block_buffer.is_empty() => String::new(), - _ => { + CodeBlockAttribute::Rust + if self.fmt.config.format_code_in_doc_comments() + && !self.code_block_buffer.is_empty() => + { let mut config = self.fmt.config.clone(); config.set().wrap_comments(false); - if config.format_code_in_doc_comments() { - if let Some(s) = - crate::format_code_block(&self.code_block_buffer, &config, false) - { - trim_custom_comment_prefix(&s.snippet) - } else { - trim_custom_comment_prefix(&self.code_block_buffer) - } + if let Some(s) = + crate::format_code_block(&self.code_block_buffer, &config, false) + { + trim_custom_comment_prefix(&s.snippet) } else { trim_custom_comment_prefix(&self.code_block_buffer) } } + _ => trim_custom_comment_prefix(&self.code_block_buffer), }; if !code_block.is_empty() { self.result.push_str(&self.comment_line_separator); diff --git a/src/config/mod.rs b/src/config/mod.rs index 8c04363b1fd..c5419d860c9 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -69,6 +69,8 @@ create_config! { format_macro_matchers: bool, false, false, "Format the metavariable matching patterns in macros"; format_macro_bodies: bool, true, false, "Format the bodies of macros"; + hex_literal_case: HexLiteralCase, HexLiteralCase::Preserve, false, + "Format hexadecimal integer literals"; // Single line expressions and items empty_item_single_line: bool, true, false, @@ -125,7 +127,7 @@ create_config! { "Add trailing semicolon after break, continue and return"; trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, false, "How to handle trailing commas for lists"; - match_block_trailing_comma: bool, false, false, + match_block_trailing_comma: bool, false, true, "Put a trailing comma after a block based match arm (non-block arms are not affected)"; blank_lines_upper_bound: usize, 1, false, "Maximum number of blank lines which can be put between items"; @@ -136,6 +138,7 @@ create_config! { inline_attribute_width: usize, 0, false, "Write an item and its attribute on the same line \ if their combined width is below a threshold"; + format_generated_files: bool, false, false, "Format generated files"; // Options that can change the source code beyond whitespace/blocks (somewhat linty things) merge_derives: bool, true, true, "Merge multiple `#[derive(...)]` into a single one"; @@ -152,7 +155,7 @@ create_config! { "Require a specific version of rustfmt"; unstable_features: bool, false, false, "Enables unstable features. Only available on nightly channel"; - disable_all_formatting: bool, false, false, "Don't reformat anything"; + disable_all_formatting: bool, false, true, "Don't reformat anything"; skip_children: bool, false, false, "Don't reformat out of line modules"; hide_parse_errors: bool, false, false, "Hide errors from the parser"; error_on_line_overflow: bool, false, false, "Error if unable to get all lines within max_width"; @@ -569,6 +572,7 @@ license_template_path = "" format_strings = false format_macro_matchers = false format_macro_bodies = true +hex_literal_case = "Preserve" empty_item_single_line = true struct_lit_single_line = true fn_single_line = false @@ -604,6 +608,7 @@ blank_lines_lower_bound = 0 edition = "2015" version = "One" inline_attribute_width = 0 +format_generated_files = false merge_derives = true use_try_shorthand = false use_field_init_shorthand = false diff --git a/src/config/options.rs b/src/config/options.rs index 3b91021813c..e92f8e8a531 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -125,6 +125,19 @@ pub enum ImportGranularity { Module, /// Use one `use` statement per imported item. Item, + /// Use one `use` statement including all items. + One, +} + +/// Controls how rustfmt should handle case in hexadecimal literals. +#[config_type] +pub enum HexLiteralCase { + /// Leave the literal as-is + Preserve, + /// Ensure all literals use uppercase lettering + Upper, + /// Ensure all literals use lowercase lettering + Lower, } #[config_type] diff --git a/src/expr.rs b/src/expr.rs index 975af6c0294..7f1dd363f93 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -13,7 +13,7 @@ use crate::comment::{ rewrite_missing_comment, CharClasses, FindUncommented, }; use crate::config::lists::*; -use crate::config::{Config, ControlBraceStyle, IndentStyle, Version}; +use crate::config::{Config, ControlBraceStyle, HexLiteralCase, IndentStyle, Version}; use crate::lists::{ definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, struct_lit_tactic, write_list, ListFormatting, Separator, @@ -822,7 +822,7 @@ impl<'a> ControlFlow<'a> { let pat_string = pat.rewrite(context, pat_shape)?; let comments_lo = context .snippet_provider - .span_after(self.span, self.connector.trim()); + .span_after(self.span.with_lo(pat.span.hi()), self.connector.trim()); let comments_span = mk_sp(comments_lo, expr.span.lo()); return rewrite_assign_rhs_with_comments( context, @@ -1168,6 +1168,7 @@ pub(crate) fn rewrite_literal( ) -> Option<String> { match l.kind { ast::LitKind::Str(_, ast::StrStyle::Cooked) => rewrite_string_lit(context, l.span, shape), + ast::LitKind::Int(..) => rewrite_int_lit(context, l, shape), _ => wrap_str( context.snippet(l.span).to_owned(), context.config.max_width(), @@ -1202,6 +1203,36 @@ fn rewrite_string_lit(context: &RewriteContext<'_>, span: Span, shape: Shape) -> ) } +fn rewrite_int_lit(context: &RewriteContext<'_>, lit: &ast::Lit, shape: Shape) -> Option<String> { + let span = lit.span; + let symbol = lit.token.symbol.as_str(); + + if symbol.starts_with("0x") { + let hex_lit = match context.config.hex_literal_case() { + HexLiteralCase::Preserve => None, + HexLiteralCase::Upper => Some(symbol[2..].to_ascii_uppercase()), + HexLiteralCase::Lower => Some(symbol[2..].to_ascii_lowercase()), + }; + if let Some(hex_lit) = hex_lit { + return wrap_str( + format!( + "0x{}{}", + hex_lit, + lit.token.suffix.map_or(String::new(), |s| s.to_string()) + ), + context.config.max_width(), + shape, + ); + } + } + + wrap_str( + context.snippet(span).to_owned(), + context.config.max_width(), + shape, + ) +} + fn choose_separator_tactic(context: &RewriteContext<'_>, span: Span) -> Option<SeparatorTactic> { if context.inside_macro() { if span_ends_with_comma(context, span) { @@ -1497,12 +1528,12 @@ fn rewrite_struct_lit<'a>( let path_shape = shape.sub_width(2)?; let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?; - let has_base = match struct_rest { + let has_base_or_rest = match struct_rest { ast::StructRest::None if fields.is_empty() => return Some(format!("{} {{}}", path_str)), ast::StructRest::Rest(_) if fields.is_empty() => { return Some(format!("{} {{ .. }}", path_str)); } - ast::StructRest::Base(_) => true, + ast::StructRest::Rest(_) | ast::StructRest::Base(_) => true, _ => false, }; @@ -1511,7 +1542,7 @@ fn rewrite_struct_lit<'a>( let one_line_width = h_shape.map_or(0, |shape| shape.width); let body_lo = context.snippet_provider.span_after(span, "{"); - let fields_str = if struct_lit_can_be_aligned(fields, has_base) + let fields_str = if struct_lit_can_be_aligned(fields, has_base_or_rest) && context.config.struct_field_align_threshold() > 0 { rewrite_with_alignment( @@ -1583,10 +1614,7 @@ fn rewrite_struct_lit<'a>( nested_shape, tactic, context, - force_no_trailing_comma - || has_base - || !context.use_block_indent() - || matches!(struct_rest, ast::StructRest::Rest(_)), + force_no_trailing_comma || has_base_or_rest || !context.use_block_indent(), ); write_list(&item_vec, &fmt)? diff --git a/src/formatting.rs b/src/formatting.rs index e0403574eeb..9ef47b887ca 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -10,6 +10,7 @@ use rustc_span::Span; use self::newline_style::apply_newline_style; use crate::comment::{CharClasses, FullCodeCharKind}; use crate::config::{Config, FileName, Verbosity}; +use crate::formatting::generated::is_generated_file; use crate::issues::BadIssueSeeker; use crate::modules::Module; use crate::syntux::parser::{DirectoryOwnership, Parser, ParserError}; @@ -18,6 +19,7 @@ use crate::utils::count_newlines; use crate::visitor::FmtVisitor; use crate::{modules, source_file, ErrorKind, FormatReport, Input, Session}; +mod generated; mod newline_style; // A map of the files of a crate, with their new content @@ -103,7 +105,12 @@ fn format_project<T: FormatHandler>( context.parse_session.set_silent_emitter(); for (path, module) in files { - let should_ignore = !input_is_stdin && context.ignore_file(&path); + let source_file = context.parse_session.span_to_file_contents(module.span); + let src = source_file.src.as_ref().expect("SourceFile without src"); + + let should_ignore = (!input_is_stdin && context.ignore_file(&path)) + || (!config.format_generated_files() && is_generated_file(src)); + if (config.skip_children() && path != main_file) || should_ignore { continue; } diff --git a/src/formatting/generated.rs b/src/formatting/generated.rs new file mode 100644 index 00000000000..58f43f17ee1 --- /dev/null +++ b/src/formatting/generated.rs @@ -0,0 +1,7 @@ +/// Returns `true` if the given span is a part of generated files. +pub(super) fn is_generated_file(original_snippet: &str) -> bool { + original_snippet + .lines() + .take(5) // looking for marker only in the beginning of the file + .any(|line| line.contains("@generated")) +} diff --git a/src/imports.rs b/src/imports.rs index 64d78605f0c..5ac79936689 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -138,6 +138,29 @@ impl UseSegment { } } + // Check if self == other with their aliases removed. + fn equal_except_alias(&self, other: &Self) -> bool { + match (self, other) { + (UseSegment::Ident(ref s1, _), UseSegment::Ident(ref s2, _)) => s1 == s2, + (UseSegment::Slf(_), UseSegment::Slf(_)) + | (UseSegment::Super(_), UseSegment::Super(_)) + | (UseSegment::Crate(_), UseSegment::Crate(_)) + | (UseSegment::Glob, UseSegment::Glob) => true, + (UseSegment::List(ref list1), UseSegment::List(ref list2)) => list1 == list2, + _ => false, + } + } + + fn get_alias(&self) -> Option<&str> { + match self { + UseSegment::Ident(_, a) + | UseSegment::Slf(a) + | UseSegment::Super(a) + | UseSegment::Crate(a) => a.as_deref(), + _ => None, + } + } + fn from_path_segment( context: &RewriteContext<'_>, path_seg: &ast::PathSegment, @@ -558,6 +581,7 @@ impl UseTree { SharedPrefix::Module => { self.path[..self.path.len() - 1] == other.path[..other.path.len() - 1] } + SharedPrefix::One => true, } } } @@ -598,7 +622,7 @@ impl UseTree { fn merge(&mut self, other: &UseTree, merge_by: SharedPrefix) { let mut prefix = 0; for (a, b) in self.path.iter().zip(other.path.iter()) { - if *a == *b { + if a.equal_except_alias(b) { prefix += 1; } else { break; @@ -633,14 +657,20 @@ fn merge_rest( return Some(new_path); } } else if len == 1 { - let rest = if a.len() == len { &b[1..] } else { &a[1..] }; - return Some(vec![ - b[0].clone(), - UseSegment::List(vec![ - UseTree::from_path(vec![UseSegment::Slf(None)], DUMMY_SP), - UseTree::from_path(rest.to_vec(), DUMMY_SP), - ]), - ]); + let (common, rest) = if a.len() == len { + (&a[0], &b[1..]) + } else { + (&b[0], &a[1..]) + }; + let mut list = vec![UseTree::from_path( + vec![UseSegment::Slf(common.get_alias().map(ToString::to_string))], + DUMMY_SP, + )]; + match rest { + [UseSegment::List(rest_list)] => list.extend(rest_list.clone()), + _ => list.push(UseTree::from_path(rest.to_vec(), DUMMY_SP)), + } + return Some(vec![b[0].clone(), UseSegment::List(list)]); } else { len -= 1; } @@ -655,18 +685,54 @@ fn merge_rest( } fn merge_use_trees_inner(trees: &mut Vec<UseTree>, use_tree: UseTree, merge_by: SharedPrefix) { - let similar_trees = trees - .iter_mut() - .filter(|tree| tree.share_prefix(&use_tree, merge_by)); + struct SimilarTree<'a> { + similarity: usize, + path_len: usize, + tree: &'a mut UseTree, + } + + let similar_trees = trees.iter_mut().filter_map(|tree| { + if tree.share_prefix(&use_tree, merge_by) { + // In the case of `SharedPrefix::One`, `similarity` is used for deciding with which + // tree `use_tree` should be merge. + // In other cases `similarity` won't be used, so set it to `0` as a dummy value. + let similarity = if merge_by == SharedPrefix::One { + tree.path + .iter() + .zip(&use_tree.path) + .take_while(|(a, b)| a.equal_except_alias(b)) + .count() + } else { + 0 + }; + + let path_len = tree.path.len(); + Some(SimilarTree { + similarity, + tree, + path_len, + }) + } else { + None + } + }); + if use_tree.path.len() == 1 && merge_by == SharedPrefix::Crate { - if let Some(tree) = similar_trees.min_by_key(|tree| tree.path.len()) { - if tree.path.len() == 1 { + if let Some(tree) = similar_trees.min_by_key(|tree| tree.path_len) { + if tree.path_len == 1 { + return; + } + } + } else if merge_by == SharedPrefix::One { + if let Some(sim_tree) = similar_trees.max_by_key(|tree| tree.similarity) { + if sim_tree.similarity > 0 { + sim_tree.tree.merge(&use_tree, merge_by); return; } } - } else if let Some(tree) = similar_trees.max_by_key(|tree| tree.path.len()) { - if tree.path.len() > 1 { - tree.merge(&use_tree, merge_by); + } else if let Some(sim_tree) = similar_trees.max_by_key(|tree| tree.path_len) { + if sim_tree.path_len > 1 { + sim_tree.tree.merge(&use_tree, merge_by); return; } } @@ -880,6 +946,7 @@ impl Rewrite for UseTree { pub(crate) enum SharedPrefix { Crate, Module, + One, } #[cfg(test)] @@ -904,7 +971,7 @@ mod test { } fn eat(&mut self, c: char) { - assert!(self.input.next().unwrap() == c); + assert_eq!(self.input.next().unwrap(), c); } fn push_segment( @@ -1095,6 +1162,49 @@ mod test { } #[test] + fn test_use_tree_merge_one() { + test_merge!(One, ["a", "b"], ["{a, b}"]); + + test_merge!(One, ["a::{aa, ab}", "b", "a"], ["{a::{self, aa, ab}, b}"]); + + test_merge!(One, ["a as x", "b as y"], ["{a as x, b as y}"]); + + test_merge!( + One, + ["a::{aa as xa, ab}", "b", "a"], + ["{a::{self, aa as xa, ab}, b}"] + ); + + test_merge!( + One, + ["a", "a::{aa, ab::{aba, abb}}"], + ["a::{self, aa, ab::{aba, abb}}"] + ); + + test_merge!(One, ["a", "b::{ba, *}"], ["{a, b::{ba, *}}"]); + + test_merge!(One, ["a", "b", "a::aa"], ["{a::{self, aa}, b}"]); + + test_merge!( + One, + ["a::aa::aaa", "a::ac::aca", "a::aa::*"], + ["a::{aa::{aaa, *}, ac::aca}"] + ); + + test_merge!( + One, + ["a", "b::{ba, bb}", "a::{aa::*, ab::aba}"], + ["{a::{self, aa::*, ab::aba}, b::{ba, bb}}"] + ); + + test_merge!( + One, + ["b", "a::ac::{aca, acb}", "a::{aa::*, ab}"], + ["{a::{aa::*, ab, ac::{aca, acb}}, b}"] + ); + } + + #[test] fn test_flatten_use_trees() { assert_eq!( flatten_use_trees(parse_use_trees!["foo::{a::{b, c}, d::e}"]), diff --git a/src/items.rs b/src/items.rs index 14041539b9d..1cb1a2701c3 100644 --- a/src/items.rs +++ b/src/items.rs @@ -174,14 +174,14 @@ pub(crate) struct FnSig<'a> { constness: ast::Const, defaultness: ast::Defaultness, unsafety: ast::Unsafe, - visibility: ast::Visibility, + visibility: &'a ast::Visibility, } impl<'a> FnSig<'a> { pub(crate) fn from_method_sig( method_sig: &'a ast::FnSig, generics: &'a ast::Generics, - visibility: ast::Visibility, + visibility: &'a ast::Visibility, ) -> FnSig<'a> { FnSig { unsafety: method_sig.header.unsafety, @@ -204,7 +204,7 @@ impl<'a> FnSig<'a> { match *fn_kind { visit::FnKind::Fn(fn_ctxt, _, fn_sig, vis, _) => match fn_ctxt { visit::FnCtxt::Assoc(..) => { - let mut fn_sig = FnSig::from_method_sig(fn_sig, generics, vis.clone()); + let mut fn_sig = FnSig::from_method_sig(fn_sig, generics, vis); fn_sig.defaultness = defaultness; fn_sig } @@ -216,7 +216,7 @@ impl<'a> FnSig<'a> { is_async: Cow::Borrowed(&fn_sig.header.asyncness), defaultness, unsafety: fn_sig.header.unsafety, - visibility: vis.clone(), + visibility: vis, }, }, _ => unreachable!(), @@ -323,6 +323,7 @@ impl<'a> FmtVisitor<'a> { indent: Indent, ident: symbol::Ident, sig: &ast::FnSig, + vis: &ast::Visibility, generics: &ast::Generics, span: Span, ) -> Option<String> { @@ -334,7 +335,7 @@ impl<'a> FmtVisitor<'a> { &context, indent, ident, - &FnSig::from_method_sig(sig, generics, DEFAULT_VISIBILITY), + &FnSig::from_method_sig(sig, generics, vis), span, FnBraceStyle::None, )?; @@ -1474,12 +1475,17 @@ fn format_tuple_struct( format_empty_struct_or_tuple(context, inner_span, offset, &mut result, "(", ")"); } else { let shape = Shape::indented(offset, context.config).sub_width(1)?; + let lo = if let Some(generics) = struct_parts.generics { + generics.span.hi() + } else { + struct_parts.ident.span.hi() + }; result = overflow::rewrite_with_parens( context, &result, fields.iter(), shape, - span, + mk_sp(lo, span.hi()), context.config.fn_call_width(), None, )?; @@ -1503,7 +1509,7 @@ fn format_tuple_struct( Some(result) } -fn rewrite_type<R: Rewrite>( +pub(crate) fn rewrite_type<R: Rewrite>( context: &RewriteContext<'_>, indent: Indent, ident: symbol::Ident, @@ -1760,7 +1766,7 @@ impl<'a> StaticParts<'a> { }; StaticParts { prefix: "const", - vis: &DEFAULT_VISIBILITY, + vis: &ti.vis, ident: ti.ident, ty, mutability: ast::Mutability::Not, @@ -1847,29 +1853,6 @@ fn rewrite_static( Some(format!("{}{};", prefix, ty_str)) } } - -pub(crate) fn rewrite_type_alias( - ident: symbol::Ident, - ty_opt: Option<&ptr::P<ast::Ty>>, - generics: &ast::Generics, - generic_bounds_opt: Option<&ast::GenericBounds>, - context: &RewriteContext<'_>, - indent: Indent, - vis: &ast::Visibility, - span: Span, -) -> Option<String> { - rewrite_type( - context, - indent, - ident, - vis, - generics, - generic_bounds_opt, - ty_opt, - span, - ) -} - struct OpaqueType<'a> { bounds: &'a ast::GenericBounds, } @@ -1883,32 +1866,7 @@ impl<'a> Rewrite for OpaqueType<'a> { } } -pub(crate) fn rewrite_opaque_impl_type( - context: &RewriteContext<'_>, - ident: symbol::Ident, - generics: &ast::Generics, - generic_bounds: &ast::GenericBounds, - indent: Indent, -) -> Option<String> { - let ident_str = rewrite_ident(context, ident); - // 5 = "type " - let generics_shape = Shape::indented(indent, context.config).offset_left(5)?; - let generics_str = rewrite_generics(context, ident_str, generics, generics_shape)?; - let prefix = format!("type {} =", generics_str); - let rhs = OpaqueType { - bounds: generic_bounds, - }; - - rewrite_assign_rhs( - context, - &prefix, - &rhs, - Shape::indented(indent, context.config).sub_width(1)?, - ) - .map(|s| s + ";") -} - -pub(crate) fn rewrite_associated_impl_type( +pub(crate) fn rewrite_impl_type( ident: symbol::Ident, vis: &ast::Visibility, defaultness: ast::Defaultness, @@ -1918,7 +1876,25 @@ pub(crate) fn rewrite_associated_impl_type( indent: Indent, span: Span, ) -> Option<String> { - let result = rewrite_type_alias(ident, ty_opt, generics, None, context, indent, vis, span)?; + // Opaque type + let result = if let Some(rustc_ast::ast::Ty { + kind: ast::TyKind::ImplTrait(_, ref bounds), + .. + }) = ty_opt.map(|t| &**t) + { + rewrite_type( + context, + indent, + ident, + &DEFAULT_VISIBILITY, + generics, + None, + Some(&OpaqueType { bounds }), + span, + ) + } else { + rewrite_type(context, indent, ident, vis, generics, None, ty_opt, span) + }?; match defaultness { ast::Defaultness::Default(..) => Some(format!("default {}", result)), @@ -1995,14 +1971,17 @@ impl Rewrite for ast::Param { let param_attrs_result = self .attrs .rewrite(context, Shape::legacy(shape.width, shape.indent))?; - let (span, has_multiple_attr_lines) = if !self.attrs.is_empty() { + // N.B. Doc comments aren't typically valid syntax, but could appear + // in the presence of certain macros - https://github.com/rust-lang/rustfmt/issues/4936 + let (span, has_multiple_attr_lines, has_doc_comments) = if !self.attrs.is_empty() { let num_attrs = self.attrs.len(); ( mk_sp(self.attrs[num_attrs - 1].span.hi(), self.pat.span.lo()), param_attrs_result.contains('\n'), + self.attrs.iter().any(|a| a.is_doc_comment()), ) } else { - (mk_sp(self.span.lo(), self.span.lo()), false) + (mk_sp(self.span.lo(), self.span.lo()), false, false) }; if let Some(ref explicit_self) = self.to_self() { @@ -2015,15 +1994,16 @@ impl Rewrite for ast::Param { has_multiple_attr_lines, ) } else if is_named_param(self) { + let param_name = &self + .pat + .rewrite(context, Shape::legacy(shape.width, shape.indent))?; let mut result = combine_strs_with_missing_comments( context, ¶m_attrs_result, - &self - .pat - .rewrite(context, Shape::legacy(shape.width, shape.indent))?, + param_name, span, shape, - !has_multiple_attr_lines, + !has_multiple_attr_lines && !has_doc_comments, )?; if !is_empty_infer(&*self.ty, self.pat.span) { @@ -2034,10 +2014,30 @@ impl Rewrite for ast::Param { result.push_str(&after_comment); let overhead = last_line_width(&result); let max_width = shape.width.checked_sub(overhead)?; - let ty_str = self + if let Some(ty_str) = self .ty - .rewrite(context, Shape::legacy(max_width, shape.indent))?; - result.push_str(&ty_str); + .rewrite(context, Shape::legacy(max_width, shape.indent)) + { + result.push_str(&ty_str); + } else { + result = combine_strs_with_missing_comments( + context, + &(param_attrs_result + &shape.to_string_with_newline(context.config)), + param_name, + span, + shape, + !has_multiple_attr_lines, + )?; + result.push_str(&before_comment); + result.push_str(colon_spaces(context.config)); + result.push_str(&after_comment); + let overhead = last_line_width(&result); + let max_width = shape.width.checked_sub(overhead)?; + let ty_str = self + .ty + .rewrite(context, Shape::legacy(max_width, shape.indent))?; + result.push_str(&ty_str); + } } Some(result) @@ -3146,7 +3146,7 @@ impl Rewrite for ast::ForeignItem { context, shape.indent, self.ident, - &FnSig::from_method_sig(&fn_sig, generics, self.vis.clone()), + &FnSig::from_method_sig(&fn_sig, generics, &self.vis), span, FnBraceStyle::None, ) @@ -3170,14 +3170,14 @@ impl Rewrite for ast::ForeignItem { ast::ForeignItemKind::TyAlias(ref ty_alias_kind) => { let ast::TyAliasKind(_, ref generics, ref generic_bounds, ref type_default) = **ty_alias_kind; - rewrite_type_alias( - self.ident, - type_default.as_ref(), - generics, - Some(generic_bounds), + rewrite_type( &context, shape.indent, + self.ident, &self.vis, + generics, + Some(generic_bounds), + type_default.as_ref(), self.span, ) } diff --git a/src/lists.rs b/src/lists.rs index 73e886c5563..c04b4787616 100644 --- a/src/lists.rs +++ b/src/lists.rs @@ -367,9 +367,9 @@ where result.push_str(&comment); if !inner_item.is_empty() { - if tactic == DefinitiveListTactic::Vertical || tactic == DefinitiveListTactic::Mixed - { - // We cannot keep pre-comments on the same line if the comment if normalized. + use DefinitiveListTactic::*; + if matches!(tactic, Vertical | Mixed | SpecialMacro(_)) { + // We cannot keep pre-comments on the same line if the comment is normalized. let keep_comment = if formatting.config.normalize_comments() || item.pre_comment_style == ListItemCommentStyle::DifferentLine { @@ -389,7 +389,7 @@ where line_len = item.item.as_ref().map_or(0, |s| unicode_str_width(&s)); } } else { - result.push(' '); + result.push(' ') } } item_max_width = None; diff --git a/src/matches.rs b/src/matches.rs index 140ec226c02..5a6ed0ec06e 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -409,6 +409,7 @@ fn rewrite_match_body( } result.push_str(&nested_indent_str); result.push_str(&body_str); + result.push_str(&comma); return Some(result); } diff --git a/src/missed_spans.rs b/src/missed_spans.rs index 263d840785a..28edcb784b4 100644 --- a/src/missed_spans.rs +++ b/src/missed_spans.rs @@ -51,6 +51,14 @@ impl<'a> FmtVisitor<'a> { } pub(crate) fn format_missing_with_indent(&mut self, end: BytePos) { + self.format_missing_indent(end, true) + } + + pub(crate) fn format_missing_no_indent(&mut self, end: BytePos) { + self.format_missing_indent(end, false) + } + + fn format_missing_indent(&mut self, end: BytePos, should_indent: bool) { let config = self.config; self.format_missing_inner(end, |this, last_snippet, snippet| { this.push_str(last_snippet.trim_end()); @@ -58,14 +66,10 @@ impl<'a> FmtVisitor<'a> { // No new lines in the snippet. this.push_str("\n"); } - let indent = this.block_indent.to_string(config); - this.push_str(&indent); - }) - } - - pub(crate) fn format_missing_no_indent(&mut self, end: BytePos) { - self.format_missing_inner(end, |this, last_snippet, _| { - this.push_str(last_snippet.trim_end()); + if should_indent { + let indent = this.block_indent.to_string(config); + this.push_str(&indent); + } }) } diff --git a/src/overflow.rs b/src/overflow.rs index e32213467a5..ac24181c780 100644 --- a/src/overflow.rs +++ b/src/overflow.rs @@ -77,6 +77,7 @@ pub(crate) enum OverflowableItem<'a> { FieldDef(&'a ast::FieldDef), TuplePatField(&'a TuplePatField<'a>), Ty(&'a ast::Ty), + Pat(&'a ast::Pat), } impl<'a> Rewrite for OverflowableItem<'a> { @@ -116,6 +117,7 @@ impl<'a> OverflowableItem<'a> { OverflowableItem::FieldDef(sf) => f(*sf), OverflowableItem::TuplePatField(pat) => f(*pat), OverflowableItem::Ty(ty) => f(*ty), + OverflowableItem::Pat(pat) => f(*pat), } } @@ -232,7 +234,7 @@ macro_rules! impl_into_overflowable_item_for_rustfmt_types { } } -impl_into_overflowable_item_for_ast_node!(Expr, GenericParam, NestedMetaItem, FieldDef, Ty); +impl_into_overflowable_item_for_ast_node!(Expr, GenericParam, NestedMetaItem, FieldDef, Ty, Pat); impl_into_overflowable_item_for_rustfmt_types!([MacroArg], [SegmentParam, TuplePatField]); pub(crate) fn into_overflowable_list<'a, T>( diff --git a/src/patterns.rs b/src/patterns.rs index 0501e76d277..ba8d8024a97 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -4,6 +4,7 @@ use rustc_span::{BytePos, Span}; use crate::comment::{combine_strs_with_missing_comments, FindUncommented}; use crate::config::lists::*; +use crate::config::Version; use crate::expr::{can_be_overflowed_expr, rewrite_unary_prefix, wrap_struct_field}; use crate::lists::{ definitive_tactic, itemize_list, shape_for_tactic, struct_lit_formatting, struct_lit_shape, @@ -226,12 +227,13 @@ impl Rewrite for Pat { PatKind::Path(ref q_self, ref path) => { rewrite_path(context, PathContext::Expr, q_self.as_ref(), path, shape) } - PatKind::TupleStruct(_, ref path, ref pat_vec) => { - let path_str = rewrite_path(context, PathContext::Expr, None, path, shape)?; + PatKind::TupleStruct(ref q_self, ref path, ref pat_vec) => { + let path_str = + rewrite_path(context, PathContext::Expr, q_self.as_ref(), path, shape)?; rewrite_tuple_pat(pat_vec, Some(path_str), self.span, context, shape) } PatKind::Lit(ref expr) => expr.rewrite(context, shape), - PatKind::Slice(ref slice_pat) => { + PatKind::Slice(ref slice_pat) if context.config.version() == Version::One => { let rw: Vec<String> = slice_pat .iter() .map(|p| { @@ -244,8 +246,17 @@ impl Rewrite for Pat { .collect(); Some(format!("[{}]", rw.join(", "))) } - PatKind::Struct(_, ref path, ref fields, ellipsis) => { - rewrite_struct_pat(path, fields, ellipsis, self.span, context, shape) + PatKind::Slice(ref slice_pat) => overflow::rewrite_with_square_brackets( + context, + "", + slice_pat.iter(), + shape, + self.span, + None, + None, + ), + PatKind::Struct(ref qself, ref path, ref fields, ellipsis) => { + rewrite_struct_pat(qself, path, fields, ellipsis, self.span, context, shape) } PatKind::MacCall(ref mac) => { rewrite_macro(mac, None, context, shape, MacroPosition::Pat) @@ -258,6 +269,7 @@ impl Rewrite for Pat { } fn rewrite_struct_pat( + qself: &Option<ast::QSelf>, path: &ast::Path, fields: &[ast::PatField], ellipsis: bool, @@ -267,7 +279,7 @@ fn rewrite_struct_pat( ) -> Option<String> { // 2 = ` {` let path_shape = shape.sub_width(2)?; - let path_str = rewrite_path(context, PathContext::Expr, None, path, path_shape)?; + let path_str = rewrite_path(context, PathContext::Expr, qself.as_ref(), path, path_shape)?; if fields.is_empty() && !ellipsis { return Some(format!("{} {{}}", path_str)); diff --git a/src/reorder.rs b/src/reorder.rs index ac65ff2c108..2c58350d4fe 100644 --- a/src/reorder.rs +++ b/src/reorder.rs @@ -113,6 +113,7 @@ fn rewrite_reorderable_or_regroupable_items( merge_use_trees(normalized_items, SharedPrefix::Module) } ImportGranularity::Item => flatten_use_trees(normalized_items), + ImportGranularity::One => merge_use_trees(normalized_items, SharedPrefix::One), ImportGranularity::Preserve => normalized_items, }; diff --git a/src/spanned.rs b/src/spanned.rs index 7e3786b7cd9..8e6c75a3744 100644 --- a/src/spanned.rs +++ b/src/spanned.rs @@ -104,7 +104,7 @@ impl Spanned for ast::Arm { impl Spanned for ast::Param { fn span(&self) -> Span { if crate::items::is_named_param(self) { - mk_sp(self.pat.span.lo(), self.ty.span.hi()) + mk_sp(crate::items::span_lo_for_param(self), self.ty.span.hi()) } else { self.ty.span } diff --git a/src/syntux/session.rs b/src/syntux/session.rs index 2965b0928aa..946c076d9f2 100644 --- a/src/syntux/session.rs +++ b/src/syntux/session.rs @@ -175,6 +175,12 @@ impl ParseSess { self.parse_sess.source_map().span_to_filename(span).into() } + pub(crate) fn span_to_file_contents(&self, span: Span) -> Lrc<rustc_span::SourceFile> { + self.parse_sess + .source_map() + .lookup_source_file(span.data().lo) + } + pub(crate) fn span_to_first_line_string(&self, span: Span) -> String { let file_lines = self.parse_sess.source_map().span_to_lines(span).ok(); diff --git a/src/test/mod.rs b/src/test/mod.rs index cb52346a13a..48d61289a9b 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -469,11 +469,6 @@ fn stdin_works_with_modified_lines() { #[test] fn stdin_disable_all_formatting_test() { init_log(); - match option_env!("CFG_RELEASE_CHANNEL") { - None | Some("nightly") => {} - // These tests require nightly. - _ => return, - } let input = String::from("fn main() { println!(\"This should not be formatted.\"); }"); let mut child = Command::new(rustfmt().to_str().unwrap()) .stdin(Stdio::piped()) @@ -694,7 +689,7 @@ fn read_significant_comments(file_name: &Path) -> HashMap<String, String> { reader .lines() .map(|line| line.expect("failed getting line")) - .take_while(|line| line_regex.is_match(line)) + .filter(|line| line_regex.is_match(line)) .filter_map(|line| { regex.captures_iter(&line).next().map(|capture| { ( diff --git a/src/types.rs b/src/types.rs index 76bf58e875b..62c05ba078c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -169,31 +169,38 @@ impl<'a> Rewrite for SegmentParam<'a> { SegmentParam::Const(const_) => const_.rewrite(context, shape), SegmentParam::LifeTime(lt) => lt.rewrite(context, shape), SegmentParam::Type(ty) => ty.rewrite(context, shape), - SegmentParam::Binding(assoc_ty_constraint) => { - let mut result = match assoc_ty_constraint.kind { - ast::AssocTyConstraintKind::Bound { .. } => { - format!("{}: ", rewrite_ident(context, assoc_ty_constraint.ident)) - } - ast::AssocTyConstraintKind::Equality { .. } => { - match context.config.type_punctuation_density() { - TypeDensity::Wide => { - format!("{} = ", rewrite_ident(context, assoc_ty_constraint.ident)) - } - TypeDensity::Compressed => { - format!("{}=", rewrite_ident(context, assoc_ty_constraint.ident)) - } - } - } - }; + SegmentParam::Binding(atc) => atc.rewrite(context, shape), + } + } +} - let budget = shape.width.checked_sub(result.len())?; - let rewrite = assoc_ty_constraint - .kind - .rewrite(context, Shape::legacy(budget, shape.indent + result.len()))?; - result.push_str(&rewrite); - Some(result) - } +impl Rewrite for ast::AssocTyConstraint { + fn rewrite(&self, context: &RewriteContext<'_>, shape: Shape) -> Option<String> { + use ast::AssocTyConstraintKind::{Bound, Equality}; + + let mut result = String::with_capacity(128); + result.push_str(rewrite_ident(context, self.ident)); + + if let Some(ref gen_args) = self.gen_args { + let budget = shape.width.checked_sub(result.len())?; + let shape = Shape::legacy(budget, shape.indent + result.len()); + let gen_str = rewrite_generic_args(gen_args, context, shape, gen_args.span())?; + result.push_str(&gen_str); } + + let infix = match (&self.kind, context.config.type_punctuation_density()) { + (Bound { .. }, _) => ": ", + (Equality { .. }, TypeDensity::Wide) => " = ", + (Equality { .. }, TypeDensity::Compressed) => "=", + }; + result.push_str(infix); + + let budget = shape.width.checked_sub(result.len())?; + let shape = Shape::legacy(budget, shape.indent + result.len()); + let rewrite = self.kind.rewrite(context, shape)?; + result.push_str(&rewrite); + + Some(result) } } @@ -235,21 +242,9 @@ fn rewrite_segment( }; if let Some(ref args) = segment.args { + let generics_str = rewrite_generic_args(args, context, shape, mk_sp(*span_lo, span_hi))?; match **args { ast::GenericArgs::AngleBracketed(ref data) if !data.args.is_empty() => { - let param_list = data - .args - .iter() - .map(|x| match x { - ast::AngleBracketedArg::Arg(generic_arg) => { - SegmentParam::from_generic_arg(generic_arg) - } - ast::AngleBracketedArg::Constraint(constraint) => { - SegmentParam::Binding(constraint) - } - }) - .collect::<Vec<_>>(); - // HACK: squeeze out the span between the identifier and the parameters. // The hack is requried so that we don't remove the separator inside macro calls. // This does not work in the presence of comment, hoping that people are @@ -265,33 +260,14 @@ fn rewrite_segment( }; result.push_str(separator); - let generics_str = overflow::rewrite_with_angle_brackets( - context, - "", - param_list.iter(), - shape, - mk_sp(*span_lo, span_hi), - )?; - // Update position of last bracket. *span_lo = context .snippet_provider .span_after(mk_sp(*span_lo, span_hi), "<"); - - result.push_str(&generics_str) - } - ast::GenericArgs::Parenthesized(ref data) => { - result.push_str(&format_function_type( - data.inputs.iter().map(|x| &**x), - &data.output, - false, - data.span, - context, - shape, - )?); } _ => (), } + result.push_str(&generics_str) } Some(result) @@ -484,6 +460,41 @@ impl Rewrite for ast::GenericArg { } } +fn rewrite_generic_args( + gen_args: &ast::GenericArgs, + context: &RewriteContext<'_>, + shape: Shape, + span: Span, +) -> Option<String> { + match gen_args { + ast::GenericArgs::AngleBracketed(ref data) if !data.args.is_empty() => { + let args = data + .args + .iter() + .map(|x| match x { + ast::AngleBracketedArg::Arg(generic_arg) => { + SegmentParam::from_generic_arg(generic_arg) + } + ast::AngleBracketedArg::Constraint(constraint) => { + SegmentParam::Binding(constraint) + } + }) + .collect::<Vec<_>>(); + + overflow::rewrite_with_angle_brackets(context, "", args.iter(), shape, span) + } + ast::GenericArgs::Parenthesized(ref data) => format_function_type( + data.inputs.iter().map(|x| &**x), + &data.output, + false, + data.span, + context, + shape, + ), + _ => Some("".to_owned()), + } +} + fn rewrite_bounded_lifetime( lt: &ast::Lifetime, bounds: &[ast::GenericBound], @@ -566,13 +577,23 @@ impl Rewrite for ast::GenericParam { if let ast::GenericParamKind::Const { ref ty, kw_span: _, - default: _, + default, } = &self.kind { result.push_str("const "); result.push_str(rewrite_ident(context, self.ident)); result.push_str(": "); result.push_str(&ty.rewrite(context, shape)?); + if let Some(default) = default { + let eq_str = match context.config.type_punctuation_density() { + TypeDensity::Compressed => "=", + TypeDensity::Wide => " = ", + }; + result.push_str(eq_str); + let budget = shape.width.checked_sub(result.len())?; + let rewrite = default.rewrite(context, Shape::legacy(budget, shape.indent))?; + result.push_str(&rewrite); + } } else { result.push_str(rewrite_ident(context, self.ident)); } diff --git a/src/visitor.rs b/src/visitor.rs index 770693d165b..d854d90b40b 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use rustc_ast::{ast, token::DelimToken, visit, AstLike}; use rustc_data_structures::sync::Lrc; -use rustc_span::{symbol, BytePos, Pos, Span, DUMMY_SP}; +use rustc_span::{symbol, BytePos, Pos, Span}; use crate::attr::*; use crate::comment::{contains_comment, rewrite_comment, CodeCharKind, CommentCodeSlices}; @@ -11,9 +11,9 @@ use crate::config::Version; use crate::config::{BraceStyle, Config}; use crate::coverage::transform_missing_snippet; use crate::items::{ - format_impl, format_trait, format_trait_alias, is_mod_decl, is_use_item, - rewrite_associated_impl_type, rewrite_extern_crate, rewrite_opaque_impl_type, - rewrite_opaque_type, rewrite_type_alias, FnBraceStyle, FnSig, StaticParts, StructParts, + format_impl, format_trait, format_trait_alias, is_mod_decl, is_use_item, rewrite_extern_crate, + rewrite_impl_type, rewrite_opaque_type, rewrite_type, FnBraceStyle, FnSig, StaticParts, + StructParts, }; use crate::macros::{macro_style, rewrite_macro, rewrite_macro_def, MacroPosition}; use crate::modules::Module; @@ -568,6 +568,7 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { indent, item.ident, &fn_signature, + &item.vis, generics, item.span, ); @@ -579,14 +580,14 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { **alias_kind; match ty { Some(ty) => { - let rewrite = rewrite_type_alias( - item.ident, - Some(&*ty), - generics, - Some(generic_bounds), + let rewrite = rewrite_type( &self.get_context(), self.block_indent, + item.ident, &item.vis, + generics, + Some(generic_bounds), + Some(&*ty), item.span, ); self.push_rewrite(item.span, rewrite); @@ -641,14 +642,9 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { let ast::FnKind(defaultness, ref sig, ref generics, ref block) = **fn_kind; if let Some(ref body) = block { let inner_attrs = inner_attributes(&ti.attrs); - let vis = ast::Visibility { - kind: ast::VisibilityKind::Inherited, - span: DUMMY_SP, - tokens: None, - }; let fn_ctxt = visit::FnCtxt::Assoc(visit::AssocCtxt::Trait); self.visit_fn( - visit::FnKind::Fn(fn_ctxt, ti.ident, sig, &vis, Some(body)), + visit::FnKind::Fn(fn_ctxt, ti.ident, sig, &ti.vis, Some(body)), generics, &sig.decl, ti.span, @@ -658,21 +654,21 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { } else { let indent = self.block_indent; let rewrite = - self.rewrite_required_fn(indent, ti.ident, sig, generics, ti.span); + self.rewrite_required_fn(indent, ti.ident, sig, &ti.vis, generics, ti.span); self.push_rewrite(ti.span, rewrite); } } ast::AssocItemKind::TyAlias(ref ty_alias_kind) => { let ast::TyAliasKind(_, ref generics, ref generic_bounds, ref type_default) = **ty_alias_kind; - let rewrite = rewrite_type_alias( - ti.ident, - type_default.as_ref(), - generics, - Some(generic_bounds), + let rewrite = rewrite_type( &self.get_context(), self.block_indent, + ti.ident, &ti.vis, + generics, + Some(generic_bounds), + type_default.as_ref(), ti.span, ); self.push_rewrite(ti.span, rewrite); @@ -708,15 +704,16 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { } else { let indent = self.block_indent; let rewrite = - self.rewrite_required_fn(indent, ii.ident, sig, generics, ii.span); + self.rewrite_required_fn(indent, ii.ident, sig, &ii.vis, generics, ii.span); self.push_rewrite(ii.span, rewrite); } } ast::AssocItemKind::Const(..) => self.visit_static(&StaticParts::from_impl_item(ii)), ast::AssocItemKind::TyAlias(ref ty_alias_kind) => { let ast::TyAliasKind(defaultness, ref generics, _, ref ty) = **ty_alias_kind; - let rewrite_associated = || { - rewrite_associated_impl_type( + self.push_rewrite( + ii.span, + rewrite_impl_type( ii.ident, &ii.vis, defaultness, @@ -725,22 +722,8 @@ impl<'b, 'a: 'b> FmtVisitor<'a> { &self.get_context(), self.block_indent, ii.span, - ) - }; - let rewrite = match ty { - None => rewrite_associated(), - Some(ty) => match ty.kind { - ast::TyKind::ImplTrait(_, ref bounds) => rewrite_opaque_impl_type( - &self.get_context(), - ii.ident, - generics, - bounds, - self.block_indent, - ), - _ => rewrite_associated(), - }, - }; - self.push_rewrite(ii.span, rewrite); + ), + ); } ast::AssocItemKind::MacCall(ref mac) => { self.visit_mac(mac, Some(ii.ident), MacroPosition::Item); |
