Merge remote-tracking branch 'upstream/master' into subtree-sync-2022-03-27

This commit is contained in:
Caleb Cartwright 2022-03-27 20:37:09 -05:00
commit 5731f1d6e1
124 changed files with 1986 additions and 268 deletions

View File

@ -40,6 +40,10 @@ jobs:
rustc -Vv
cargo -V
cargo build
env:
RUSTFLAGS: '-D warnings'
- name: test
run: cargo test
env:
RUSTFLAGS: '-D warnings'

View File

@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixes issue where wrapped strings would be incorrectly indented in macro defs when `format_strings` was enabled [#4036](https://github.com/rust-lang/rustfmt/issues/4036)
## [1.4.38] 2021-10-20
### Changed
@ -57,6 +61,7 @@ Note this hit the rustup distributions prior to the v1.4.38 release as part of a
- New `One` variant added to `imports_granularity` configuration option which can be used to reformat all imports into a single use statement [#4669](https://github.com/rust-lang/rustfmt/issues/4669)
- rustfmt will now skip files that are annotated with `@generated` at the top of the file [#3958](https://github.com/rust-lang/rustfmt/issues/3958)
if `format_generated_files` option is set to `false` (by default `@generated` files are formatted)
- New configuration option `hex_literal_case` that allows user to control the casing utilized for hex literals [PR #4903](https://github.com/rust-lang/rustfmt/pull/4903)
See the section on the configuration site for more information

4
Cargo.lock generated
View File

@ -275,9 +275,9 @@ dependencies = [
[[package]]
name = "itertools"
version = "0.9.0"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]

View File

@ -930,6 +930,8 @@ fn add_one(x: i32) -> i32 {
Format generated files. A file is considered generated
if any of the first five lines contain a `@generated` comment marker.
By default, generated files are reformatted, i. e. `@generated` marker is ignored.
This option is currently ignored for stdin (`@generated` in stdin is ignored.)
- **Default value**: `true`
- **Possible values**: `true`, `false`
@ -2198,13 +2200,47 @@ specific version of rustfmt is used in your CI, use this option.
- **Possible values**: any published version (e.g. `"0.3.8"`)
- **Stable**: No (tracking issue: [#3386](https://github.com/rust-lang/rustfmt/issues/3386))
## `short_array_element_width_threshold`
The width threshold for an array element to be considered "short".
The layout of an array is dependent on the length of each of its elements.
If the length of every element in an array is below this threshold (all elements are "short") then the array can be formatted in the mixed/compressed style, but if any one element has a length that exceeds this threshold then the array elements will have to be formatted vertically.
- **Default value**: `10`
- **Possible values**: any positive integer that is less than or equal to the value specified for [`max_width`](#max_width)
- **Stable**: Yes
#### `10` (default):
```rust
fn main() {
pub const FORMAT_TEST: [u64; 5] = [
0x0000000000000000,
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
0xcccccccccccccccc,
0xdddddddddddddddd,
];
}
```
#### `20`:
```rust
fn main() {
pub const FORMAT_TEST: [u64; 5] = [
0x0000000000000000, 0xaaaaaaaaaaaaaaaa, 0xbbbbbbbbbbbbbbbb, 0xcccccccccccccccc,
0xdddddddddddddddd,
];
}
```
See also [`max_width`](#max_width).
## `skip_children`
Don't reformat out of line modules
- **Default value**: `false`
- **Possible values**: `true`, `false`
- **Stable**: No (tracking issue: [#3389](https://github.com/rust-lang/rustfmt/issues/3386))
- **Stable**: No (tracking issue: [#3389](https://github.com/rust-lang/rustfmt/issues/3389))
## `single_line_if_else_max_width`

View File

@ -22,10 +22,10 @@ pub fn is_unit(v: &syn::Variant) -> bool {
#[cfg(feature = "debug-with-rustfmt")]
/// Pretty-print the output of proc macro using rustfmt.
pub fn debug_with_rustfmt(input: &TokenStream) {
use std::io::Write;
use std::process::{Command, Stdio};
use std::env;
use std::ffi::OsStr;
use std::io::Write;
use std::process::{Command, Stdio};
let rustfmt_var = env::var_os("RUSTFMT");
let rustfmt = match &rustfmt_var {

View File

@ -1,3 +1,3 @@
[toolchain]
channel = "nightly-2021-12-29"
channel = "nightly-2022-03-17"
components = ["rustc-dev"]

View File

@ -389,6 +389,10 @@ impl Rewrite for [ast::Attribute] {
let mut attrs = self;
let mut result = String::new();
// Determine if the source text is annotated with `#[rustfmt::skip::attributes(derive)]`
// or `#![rustfmt::skip::attributes(derive)]`
let skip_derives = context.skip_context.skip_attribute("derive");
// This is not just a simple map because we need to handle doc comments
// (where we take as many doc comment attributes as possible) and possibly
// merging derives into a single attribute.
@ -431,7 +435,7 @@ impl Rewrite for [ast::Attribute] {
}
// Handle derives if we will merge them.
if context.config.merge_derives() && is_derive(&attrs[0]) {
if !skip_derives && context.config.merge_derives() && is_derive(&attrs[0]) {
let derives = take_while_with_pred(context, attrs, is_derive);
let derive_str = format_derive(derives, shape, context)?;
result.push_str(&derive_str);

View File

@ -26,7 +26,7 @@ fn main() {
let exit_code = match execute(&opts) {
Ok(code) => code,
Err(e) => {
eprintln!("{}", e);
eprintln!("{:#}", e);
1
}
};
@ -74,14 +74,10 @@ pub enum OperationError {
/// An io error during reading or writing.
#[error("{0}")]
IoError(IoError),
/// Attempt to use --check with stdin, which isn't currently
/// supported.
#[error("The `--check` option is not supported with standard input.")]
CheckWithStdin,
/// Attempt to use --emit=json with stdin, which isn't currently
/// supported.
#[error("Using `--emit` other than stdout is not supported with standard input.")]
EmitWithStdin,
/// Attempt to use --emit with a mode which is not currently
/// supported with stdandard input.
#[error("Emit mode {0} not supported with standard output.")]
StdinBadEmit(EmitMode),
}
impl From<IoError> for OperationError {
@ -255,15 +251,20 @@ fn format_string(input: String, options: GetOptsOptions) -> Result<i32> {
let (mut config, _) = load_config(Some(Path::new(".")), Some(options.clone()))?;
if options.check {
return Err(OperationError::CheckWithStdin.into());
}
if let Some(emit_mode) = options.emit_mode {
if emit_mode != EmitMode::Stdout {
return Err(OperationError::EmitWithStdin.into());
config.set().emit_mode(EmitMode::Diff);
} else {
match options.emit_mode {
// Emit modes which work with standard input
// None means default, which is Stdout.
None | Some(EmitMode::Stdout) | Some(EmitMode::Checkstyle) | Some(EmitMode::Json) => {}
Some(emit_mode) => {
return Err(OperationError::StdinBadEmit(emit_mode).into());
}
}
config
.set()
.emit_mode(options.emit_mode.unwrap_or(EmitMode::Stdout));
}
// emit mode is always Stdout for Stdin.
config.set().emit_mode(EmitMode::Stdout);
config.set().verbose(Verbosity::Quiet);
// parse file_lines
@ -393,9 +394,8 @@ fn print_usage_to_stdout(opts: &Options, reason: &str) {
format!("{}\n\n", reason)
};
let msg = format!(
"{}Format Rust code\n\nusage: {} [options] <file>...",
sep,
env::args_os().next().unwrap().to_string_lossy()
"{}Format Rust code\n\nusage: rustfmt [options] <file>...",
sep
);
println!("{}", opts.usage(&msg));
}

View File

@ -387,8 +387,7 @@ fn get_targets_root_only(
.unwrap_or_default()
== current_dir_manifest
})
.map(|p| p.targets)
.flatten()
.flat_map(|p| p.targets)
.collect(),
};

View File

@ -432,16 +432,16 @@ impl CodeBlockAttribute {
/// Block that is formatted as an item.
///
/// An item starts with either a star `*` or a dash `-`. Different level of indentation are
/// handled by shrinking the shape accordingly.
/// An item starts with either a star `*` a dash `-` or a greater-than `>`.
/// Different level of indentation are handled by shrinking the shape accordingly.
struct ItemizedBlock {
/// the lines that are identified as part of an itemized block
lines: Vec<String>,
/// the number of whitespaces up to the item sigil
/// the number of characters (typically whitespaces) up to the item sigil
indent: usize,
/// the string that marks the start of an item
opener: String,
/// sequence of whitespaces to prefix new lines that are part of the item
/// sequence of characters (typically whitespaces) to prefix new lines that are part of the item
line_start: String,
}
@ -449,19 +449,32 @@ impl ItemizedBlock {
/// Returns `true` if the line is formatted as an item
fn is_itemized_line(line: &str) -> bool {
let trimmed = line.trim_start();
trimmed.starts_with("* ") || trimmed.starts_with("- ")
trimmed.starts_with("* ") || trimmed.starts_with("- ") || trimmed.starts_with("> ")
}
/// Creates a new ItemizedBlock described with the given line.
/// The `is_itemized_line` needs to be called first.
fn new(line: &str) -> ItemizedBlock {
let space_to_sigil = line.chars().take_while(|c| c.is_whitespace()).count();
let indent = space_to_sigil + 2;
// +2 = '* ', which will add the appropriate amount of whitespace to keep itemized
// content formatted correctly.
let mut indent = space_to_sigil + 2;
let mut line_start = " ".repeat(indent);
// Markdown blockquote start with a "> "
if line.trim_start().starts_with(">") {
// remove the original +2 indent because there might be multiple nested block quotes
// and it's easier to reason about the final indent by just taking the length
// of th new line_start. We update the indent because it effects the max width
// of each formatted line.
line_start = itemized_block_quote_start(line, line_start, 2);
indent = line_start.len();
}
ItemizedBlock {
lines: vec![line[indent..].to_string()],
indent,
opener: line[..indent].to_string(),
line_start: " ".repeat(indent),
line_start,
}
}
@ -504,6 +517,25 @@ impl ItemizedBlock {
}
}
/// Determine the line_start when formatting markdown block quotes.
/// The original line_start likely contains indentation (whitespaces), which we'd like to
/// replace with '> ' characters.
fn itemized_block_quote_start(line: &str, mut line_start: String, remove_indent: usize) -> String {
let quote_level = line
.chars()
.take_while(|c| !c.is_alphanumeric())
.fold(0, |acc, c| if c == '>' { acc + 1 } else { acc });
for _ in 0..remove_indent {
line_start.pop();
}
for _ in 0..quote_level {
line_start.push_str("> ")
}
line_start
}
struct CommentRewrite<'a> {
result: String,
code_block_buffer: String,
@ -651,6 +683,7 @@ impl<'a> CommentRewrite<'a> {
i: usize,
line: &'a str,
has_leading_whitespace: bool,
is_doc_comment: bool,
) -> bool {
let num_newlines = count_newlines(orig);
let is_last = i == num_newlines;
@ -757,10 +790,19 @@ impl<'a> CommentRewrite<'a> {
}
}
if self.fmt.config.wrap_comments()
let is_markdown_header_doc_comment = is_doc_comment && line.starts_with("#");
// We only want to wrap the comment if:
// 1) wrap_comments = true is configured
// 2) The comment is not the start of a markdown header doc comment
// 3) The comment width exceeds the shape's width
// 4) No URLS were found in the commnet
let should_wrap_comment = self.fmt.config.wrap_comments()
&& !is_markdown_header_doc_comment
&& unicode_str_width(line) > self.fmt.shape.width
&& !has_url(line)
{
&& !has_url(line);
if should_wrap_comment {
match rewrite_string(line, &self.fmt, self.max_width) {
Some(ref s) => {
self.is_prev_line_multi_line = s.contains('\n');
@ -850,7 +892,7 @@ fn rewrite_comment_inner(
});
for (i, (line, has_leading_whitespace)) in lines.enumerate() {
if rewriter.handle_line(orig, i, line, has_leading_whitespace) {
if rewriter.handle_line(orig, i, line, has_leading_whitespace, is_doc_comment) {
break;
}
}

View File

@ -39,7 +39,7 @@ impl fmt::Display for FileName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FileName::Real(p) => write!(f, "{}", p.to_str().unwrap()),
FileName::Stdin => write!(f, "stdin"),
FileName::Stdin => write!(f, "<stdin>"),
}
}
}

View File

@ -106,6 +106,8 @@ create_config! {
// Misc.
remove_nested_parens: bool, true, true, "Remove nested parens";
combine_control_expr: bool, true, false, "Combine control expressions with function calls";
short_array_element_width_threshold: usize, 10, true,
"Width threshold for an array element to be considered short";
overflow_delimited_expr: bool, false, false,
"Allow trailing bracket/brace delimited expressions to overflow";
struct_field_align_threshold: usize, 0, false,
@ -364,7 +366,9 @@ fn get_toml_path(dir: &Path) -> Result<Option<PathBuf>, Error> {
// find the project file yet, and continue searching.
Err(e) => {
if e.kind() != ErrorKind::NotFound {
return Err(e);
let ctx = format!("Failed to get metadata for config file {:?}", &config_file);
let err = anyhow::Error::new(e).context(ctx);
return Err(Error::new(ErrorKind::Other, err));
}
}
_ => {}
@ -589,6 +593,7 @@ spaces_around_ranges = false
binop_separator = "Front"
remove_nested_parens = true
combine_control_expr = true
short_array_element_width_threshold = 10
overflow_delimited_expr = false
struct_field_align_threshold = 0
enum_discrim_align_threshold = 0

View File

@ -2,7 +2,6 @@ use self::xml::XmlEscaped;
use super::*;
use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch};
use std::io::{self, Write};
use std::path::Path;
mod xml;
@ -30,7 +29,6 @@ impl Emitter for CheckstyleEmitter {
}: FormattedFile<'_>,
) -> Result<EmitterResult, io::Error> {
const CONTEXT_SIZE: usize = 0;
let filename = ensure_real_path(filename);
let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE);
output_checkstyle_file(output, filename, diff)?;
Ok(EmitterResult::default())
@ -39,13 +37,13 @@ impl Emitter for CheckstyleEmitter {
pub(crate) fn output_checkstyle_file<T>(
mut writer: T,
filename: &Path,
filename: &FileName,
diff: Vec<Mismatch>,
) -> Result<(), io::Error>
where
T: Write,
{
write!(writer, r#"<file name="{}">"#, filename.display())?;
write!(writer, r#"<file name="{}">"#, filename)?;
for mismatch in diff {
let begin_line = mismatch.line_number;
let mut current_line;
@ -77,7 +75,11 @@ mod tests {
fn emits_empty_record_on_file_with_no_mismatches() {
let file_name = "src/well_formatted.rs";
let mut writer = Vec::new();
let _ = output_checkstyle_file(&mut writer, &PathBuf::from(file_name), vec![]);
let _ = output_checkstyle_file(
&mut writer,
&FileName::Real(PathBuf::from(file_name)),
vec![],
);
assert_eq!(
&writer[..],
format!(r#"<file name="{}"></file>"#, file_name).as_bytes()

View File

@ -28,7 +28,7 @@ impl Emitter for DiffEmitter {
if has_diff {
if self.config.print_misformatted_file_names() {
writeln!(output, "{}", ensure_real_path(filename).display())?;
writeln!(output, "{}", filename)?;
} else {
print_diff(
mismatch,
@ -40,8 +40,7 @@ impl Emitter for DiffEmitter {
// This occurs when the only difference between the original and formatted values
// is the newline style. This happens because The make_diff function compares the
// original and formatted values line by line, independent of line endings.
let file_path = ensure_real_path(filename);
writeln!(output, "Incorrect newline style in {}", file_path.display())?;
writeln!(output, "Incorrect newline style in {}", filename)?;
return Ok(EmitterResult { has_diff: true });
}

View File

@ -3,14 +3,13 @@ use crate::rustfmt_diff::{make_diff, DiffLine, Mismatch};
use serde::Serialize;
use serde_json::to_string as to_json_string;
use std::io::{self, Write};
use std::path::Path;
#[derive(Debug, Default)]
pub(crate) struct JsonEmitter {
num_files: u32,
mismatched_files: Vec<MismatchedFile>,
}
#[derive(Debug, Default, Serialize)]
#[derive(Debug, Default, PartialEq, Serialize)]
struct MismatchedBlock {
original_begin_line: u32,
original_end_line: u32,
@ -20,26 +19,20 @@ struct MismatchedBlock {
expected: String,
}
#[derive(Debug, Default, Serialize)]
#[derive(Debug, Default, PartialEq, Serialize)]
struct MismatchedFile {
name: String,
mismatches: Vec<MismatchedBlock>,
}
impl Emitter for JsonEmitter {
fn emit_header(&self, output: &mut dyn Write) -> Result<(), io::Error> {
write!(output, "[")?;
Ok(())
}
fn emit_footer(&self, output: &mut dyn Write) -> Result<(), io::Error> {
write!(output, "]")?;
Ok(())
writeln!(output, "{}", &to_json_string(&self.mismatched_files)?)
}
fn emit_formatted_file(
&mut self,
output: &mut dyn Write,
_output: &mut dyn Write,
FormattedFile {
filename,
original_text,
@ -47,71 +40,67 @@ impl Emitter for JsonEmitter {
}: FormattedFile<'_>,
) -> Result<EmitterResult, io::Error> {
const CONTEXT_SIZE: usize = 0;
let filename = ensure_real_path(filename);
let diff = make_diff(original_text, formatted_text, CONTEXT_SIZE);
let has_diff = !diff.is_empty();
if has_diff {
output_json_file(output, filename, diff, self.num_files)?;
self.num_files += 1;
self.add_misformatted_file(filename, diff)?;
}
Ok(EmitterResult { has_diff })
}
}
fn output_json_file<T>(
mut writer: T,
filename: &Path,
diff: Vec<Mismatch>,
num_emitted_files: u32,
) -> Result<(), io::Error>
where
T: Write,
{
let mut mismatches = vec![];
for mismatch in diff {
let original_begin_line = mismatch.line_number_orig;
let expected_begin_line = mismatch.line_number;
let mut original_end_line = original_begin_line;
let mut expected_end_line = expected_begin_line;
let mut original_line_counter = 0;
let mut expected_line_counter = 0;
let mut original_lines = vec![];
let mut expected_lines = vec![];
impl JsonEmitter {
fn add_misformatted_file(
&mut self,
filename: &FileName,
diff: Vec<Mismatch>,
) -> Result<(), io::Error> {
let mut mismatches = vec![];
for mismatch in diff {
let original_begin_line = mismatch.line_number_orig;
let expected_begin_line = mismatch.line_number;
let mut original_end_line = original_begin_line;
let mut expected_end_line = expected_begin_line;
let mut original_line_counter = 0;
let mut expected_line_counter = 0;
let mut original = String::new();
let mut expected = String::new();
for line in mismatch.lines {
match line {
DiffLine::Expected(msg) => {
expected_end_line = expected_begin_line + expected_line_counter;
expected_line_counter += 1;
expected_lines.push(msg)
for line in mismatch.lines {
match line {
DiffLine::Expected(msg) => {
expected_end_line = expected_begin_line + expected_line_counter;
expected_line_counter += 1;
expected.push_str(&msg);
expected.push('\n');
}
DiffLine::Resulting(msg) => {
original_end_line = original_begin_line + original_line_counter;
original_line_counter += 1;
original.push_str(&msg);
original.push('\n');
}
DiffLine::Context(_) => continue,
}
DiffLine::Resulting(msg) => {
original_end_line = original_begin_line + original_line_counter;
original_line_counter += 1;
original_lines.push(msg)
}
DiffLine::Context(_) => continue,
}
}
mismatches.push(MismatchedBlock {
original_begin_line,
original_end_line,
expected_begin_line,
expected_end_line,
original: original_lines.join("\n"),
expected: expected_lines.join("\n"),
mismatches.push(MismatchedBlock {
original_begin_line,
original_end_line,
expected_begin_line,
expected_end_line,
original,
expected,
});
}
self.mismatched_files.push(MismatchedFile {
name: format!("{}", filename),
mismatches,
});
Ok(())
}
let json = to_json_string(&MismatchedFile {
name: String::from(filename.to_str().unwrap()),
mismatches,
})?;
let prefix = if num_emitted_files > 0 { "," } else { "" };
write!(writer, "{}{}", prefix, &json)?;
Ok(())
}
#[cfg(test)]
@ -122,6 +111,9 @@ mod tests {
#[test]
fn expected_line_range_correct_when_single_line_split() {
let mut emitter = JsonEmitter {
mismatched_files: vec![],
};
let file = "foo/bar.rs";
let mismatched_file = MismatchedFile {
name: String::from(file),
@ -130,8 +122,8 @@ mod tests {
original_end_line: 79,
expected_begin_line: 79,
expected_end_line: 82,
original: String::from("fn Foo<T>() where T: Bar {"),
expected: String::from("fn Foo<T>()\nwhere\n T: Bar,\n{"),
original: String::from("fn Foo<T>() where T: Bar {\n"),
expected: String::from("fn Foo<T>()\nwhere\n T: Bar,\n{\n"),
}],
};
let mismatch = Mismatch {
@ -146,14 +138,19 @@ mod tests {
],
};
let mut writer = Vec::new();
let exp_json = to_json_string(&mismatched_file).unwrap();
let _ = output_json_file(&mut writer, &PathBuf::from(file), vec![mismatch], 0);
assert_eq!(&writer[..], format!("{}", exp_json).as_bytes());
let _ = emitter
.add_misformatted_file(&FileName::Real(PathBuf::from(file)), vec![mismatch])
.unwrap();
assert_eq!(emitter.mismatched_files.len(), 1);
assert_eq!(emitter.mismatched_files[0], mismatched_file);
}
#[test]
fn context_lines_ignored() {
let mut emitter = JsonEmitter {
mismatched_files: vec![],
};
let file = "src/lib.rs";
let mismatched_file = MismatchedFile {
name: String::from(file),
@ -163,10 +160,10 @@ mod tests {
expected_begin_line: 5,
expected_end_line: 5,
original: String::from(
"fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {",
"fn foo(_x: &u64) -> Option<&(dyn::std::error::Error + 'static)> {\n",
),
expected: String::from(
"fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {",
"fn foo(_x: &u64) -> Option<&(dyn ::std::error::Error + 'static)> {\n",
),
}],
};
@ -186,10 +183,12 @@ mod tests {
],
};
let mut writer = Vec::new();
let exp_json = to_json_string(&mismatched_file).unwrap();
let _ = output_json_file(&mut writer, &PathBuf::from(file), vec![mismatch], 0);
assert_eq!(&writer[..], format!("{}", exp_json).as_bytes());
let _ = emitter
.add_misformatted_file(&FileName::Real(PathBuf::from(file)), vec![mismatch])
.unwrap();
assert_eq!(emitter.mismatched_files.len(), 1);
assert_eq!(emitter.mismatched_files[0], mismatched_file);
}
#[test]
@ -209,7 +208,7 @@ mod tests {
.unwrap();
let _ = emitter.emit_footer(&mut writer);
assert_eq!(result.has_diff, false);
assert_eq!(&writer[..], "[]".as_bytes());
assert_eq!(&writer[..], "[]\n".as_bytes());
}
#[test]
@ -255,7 +254,7 @@ mod tests {
)
.unwrap();
let _ = emitter.emit_footer(&mut writer);
let exp_json = to_json_string(&MismatchedFile {
let exp_json = to_json_string(&vec![MismatchedFile {
name: String::from(file_name),
mismatches: vec![
MismatchedBlock {
@ -263,8 +262,8 @@ mod tests {
original_end_line: 2,
expected_begin_line: 2,
expected_end_line: 2,
original: String::from("println!(\"Hello, world!\");"),
expected: String::from(" println!(\"Hello, world!\");"),
original: String::from("println!(\"Hello, world!\");\n"),
expected: String::from(" println!(\"Hello, world!\");\n"),
},
MismatchedBlock {
original_begin_line: 7,
@ -272,17 +271,17 @@ mod tests {
expected_begin_line: 7,
expected_end_line: 10,
original: String::from(
"#[test]\nfn it_works() {\n assert_eq!(2 + 2, 4);\n}",
"#[test]\nfn it_works() {\n assert_eq!(2 + 2, 4);\n}\n",
),
expected: String::from(
" #[test]\n fn it_works() {\n assert_eq!(2 + 2, 4);\n }",
" #[test]\n fn it_works() {\n assert_eq!(2 + 2, 4);\n }\n",
),
},
],
})
}])
.unwrap();
assert_eq!(result.has_diff, true);
assert_eq!(&writer[..], format!("[{}]", exp_json).as_bytes());
assert_eq!(&writer[..], format!("{}\n", exp_json).as_bytes());
}
#[test]
@ -317,33 +316,31 @@ mod tests {
)
.unwrap();
let _ = emitter.emit_footer(&mut writer);
let exp_bin_json = to_json_string(&MismatchedFile {
let exp_bin = MismatchedFile {
name: String::from(bin_file),
mismatches: vec![MismatchedBlock {
original_begin_line: 2,
original_end_line: 2,
expected_begin_line: 2,
expected_end_line: 2,
original: String::from("println!(\"Hello, world!\");"),
expected: String::from(" println!(\"Hello, world!\");"),
original: String::from("println!(\"Hello, world!\");\n"),
expected: String::from(" println!(\"Hello, world!\");\n"),
}],
})
.unwrap();
let exp_lib_json = to_json_string(&MismatchedFile {
};
let exp_lib = MismatchedFile {
name: String::from(lib_file),
mismatches: vec![MismatchedBlock {
original_begin_line: 2,
original_end_line: 2,
expected_begin_line: 2,
expected_end_line: 2,
original: String::from("println!(\"Greetings!\");"),
expected: String::from(" println!(\"Greetings!\");"),
original: String::from("println!(\"Greetings!\");\n"),
expected: String::from(" println!(\"Greetings!\");\n"),
}],
})
.unwrap();
assert_eq!(
&writer[..],
format!("[{},{}]", exp_bin_json, exp_lib_json).as_bytes()
);
};
let exp_json = to_json_string(&vec![exp_bin, exp_lib]).unwrap();
assert_eq!(&writer[..], format!("{}\n", exp_json).as_bytes());
}
}

View File

@ -20,6 +20,7 @@ impl<'a> FormatReportFormatterBuilder<'a> {
}
/// Enables colors and formatting in the output.
#[must_use]
pub fn enable_colors(self, enable_colors: bool) -> Self {
Self {
enable_colors,

View File

@ -32,16 +32,15 @@ impl IgnorePathSet {
#[cfg(test)]
mod test {
use std::path::{Path, PathBuf};
use crate::config::{Config, FileName};
use crate::ignore_path::IgnorePathSet;
use rustfmt_config_proc_macro::nightly_only_test;
#[nightly_only_test]
#[test]
fn test_ignore_path_set() {
use crate::config::{Config, FileName};
use crate::ignore_path::IgnorePathSet;
use std::path::{Path, PathBuf};
let config =
Config::from_toml(r#"ignore = ["foo.rs", "bar_dir/*"]"#, Path::new("")).unwrap();
let ignore_path_set = IgnorePathSet::from_ignore_list(&config.ignore()).unwrap();

View File

@ -190,13 +190,17 @@ pub(crate) fn merge_use_trees(use_trees: Vec<UseTree>, merge_by: SharedPrefix) -
continue;
}
for flattened in use_tree.flatten() {
for mut flattened in use_tree.flatten() {
if let Some(tree) = result
.iter_mut()
.find(|tree| tree.share_prefix(&flattened, merge_by))
{
tree.merge(&flattened, merge_by);
} else {
// If this is the first tree with this prefix, handle potential trailing ::self
if merge_by == SharedPrefix::Module {
flattened = flattened.nest_trailing_self();
}
result.push(flattened);
}
}
@ -208,17 +212,7 @@ pub(crate) fn flatten_use_trees(use_trees: Vec<UseTree>) -> Vec<UseTree> {
use_trees
.into_iter()
.flat_map(UseTree::flatten)
.map(|mut tree| {
// If a path ends in `::self`, rewrite it to `::{self}`.
if let Some(UseSegment::Slf(..)) = tree.path.last() {
let self_segment = tree.path.pop().unwrap();
tree.path.push(UseSegment::List(vec![UseTree::from_path(
vec![self_segment],
DUMMY_SP,
)]));
}
tree
})
.map(UseTree::nest_trailing_self)
.collect()
}
@ -238,7 +232,8 @@ impl fmt::Display for UseSegment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
UseSegment::Glob => write!(f, "*"),
UseSegment::Ident(ref s, _) => write!(f, "{}", s),
UseSegment::Ident(ref s, Some(ref alias)) => write!(f, "{} as {}", s, alias),
UseSegment::Ident(ref s, None) => write!(f, "{}", s),
UseSegment::Slf(..) => write!(f, "self"),
UseSegment::Super(..) => write!(f, "super"),
UseSegment::Crate(..) => write!(f, "crate"),
@ -622,7 +617,8 @@ 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.equal_except_alias(b) {
// only discard the alias at the root of the tree
if (prefix == 0 && a.equal_except_alias(b)) || a == b {
prefix += 1;
} else {
break;
@ -633,6 +629,18 @@ impl UseTree {
self.span = self.span.to(other.span);
}
}
/// If this tree ends in `::self`, rewrite it to `::{self}`.
fn nest_trailing_self(mut self) -> UseTree {
if let Some(UseSegment::Slf(..)) = self.path.last() {
let self_segment = self.path.pop().unwrap();
self.path.push(UseSegment::List(vec![UseTree::from_path(
vec![self_segment],
DUMMY_SP,
)]));
}
self
}
}
fn merge_rest(
@ -1309,4 +1317,24 @@ mod test {
< parse_use_tree("std::cmp::{b, e, g, f}").normalize()
);
}
#[test]
fn test_use_tree_nest_trailing_self() {
assert_eq!(
parse_use_tree("a::b::self").nest_trailing_self(),
parse_use_tree("a::b::{self}")
);
assert_eq!(
parse_use_tree("a::b::c").nest_trailing_self(),
parse_use_tree("a::b::c")
);
assert_eq!(
parse_use_tree("a::b::{c, d}").nest_trailing_self(),
parse_use_tree("a::b::{c, d}")
);
assert_eq!(
parse_use_tree("a::b::{self, c}").nest_trailing_self(),
parse_use_tree("a::b::{self, c}")
);
}
}

View File

@ -1273,7 +1273,13 @@ pub(crate) fn format_struct_struct(
result.push_str(&header_str);
let header_hi = struct_parts.ident.span.hi();
let body_lo = context.snippet_provider.span_after(span, "{");
let body_lo = if let Some(generics) = struct_parts.generics {
// Adjust the span to start at the end of the generic arguments before searching for the '{'
let span = span.with_lo(generics.span.hi());
context.snippet_provider.span_after(span, "{")
} else {
context.snippet_provider.span_after(span, "{")
};
let generics_str = match struct_parts.generics {
Some(g) => format_generics(
@ -1377,17 +1383,21 @@ fn format_empty_struct_or_tuple(
result.push_str(&offset.to_string_with_newline(context.config))
}
result.push_str(opener);
match rewrite_missing_comment(span, Shape::indented(offset, context.config), context) {
// indented shape for proper indenting of multi-line comments
let shape = Shape::indented(offset.block_indent(context.config), context.config);
match rewrite_missing_comment(span, shape, context) {
Some(ref s) if s.is_empty() => (),
Some(ref s) => {
if !is_single_line(s) || first_line_contains_single_line_comment(s) {
let is_multi_line = !is_single_line(s);
if is_multi_line || first_line_contains_single_line_comment(s) {
let nested_indent_str = offset
.block_indent(context.config)
.to_string_with_newline(context.config);
result.push_str(&nested_indent_str);
}
result.push_str(s);
if last_line_contains_single_line_comment(s) {
if is_multi_line || last_line_contains_single_line_comment(s) {
result.push_str(&offset.to_string_with_newline(context.config));
}
}
@ -2046,9 +2056,15 @@ impl Rewrite for ast::Param {
{
result.push_str(&ty_str);
} else {
let prev_str = if param_attrs_result.is_empty() {
param_attrs_result
} else {
param_attrs_result + &shape.to_string_with_newline(context.config)
};
result = combine_strs_with_missing_comments(
context,
&(param_attrs_result + &shape.to_string_with_newline(context.config)),
&prev_str,
param_name,
span,
shape,

View File

@ -3,7 +3,6 @@
#![warn(unreachable_pub)]
#![recursion_limit = "256"]
#![allow(clippy::match_like_matches_macro)]
#![allow(unreachable_pub)]
#[macro_use]
extern crate derive_new;

View File

@ -611,15 +611,30 @@ pub(crate) fn extract_post_comment(
post_snippet: &str,
comment_end: usize,
separator: &str,
is_last: bool,
) -> Option<String> {
let white_space: &[_] = &[' ', '\t'];
// Cleanup post-comment: strip separators and whitespace.
let post_snippet = post_snippet[..comment_end].trim();
let last_inline_comment_ends_with_separator = if is_last {
if let Some(line) = post_snippet.lines().last() {
line.ends_with(separator) && line.trim().starts_with("//")
} else {
false
}
} else {
false
};
let post_snippet_trimmed = if post_snippet.starts_with(|c| c == ',' || c == ':') {
post_snippet[1..].trim_matches(white_space)
} else if let Some(stripped) = post_snippet.strip_prefix(separator) {
stripped.trim_matches(white_space)
} else if last_inline_comment_ends_with_separator {
// since we're on the last item it's fine to keep any trailing separators in comments
post_snippet.trim_matches(white_space)
}
// not comment or over two lines
else if post_snippet.ends_with(',')
@ -748,14 +763,12 @@ where
.snippet_provider
.span_to_snippet(mk_sp((self.get_hi)(&item), next_start))
.unwrap_or("");
let comment_end = get_comment_end(
post_snippet,
self.separator,
self.terminator,
self.inner.peek().is_none(),
);
let is_last = self.inner.peek().is_none();
let comment_end =
get_comment_end(post_snippet, self.separator, self.terminator, is_last);
let new_lines = has_extra_newline(post_snippet, comment_end);
let post_comment = extract_post_comment(post_snippet, comment_end, self.separator);
let post_comment =
extract_post_comment(post_snippet, comment_end, self.separator, is_last);
self.prev_span_end = (self.get_hi)(&item) + BytePos(comment_end as u32);

View File

@ -152,7 +152,7 @@ pub(crate) fn rewrite_macro(
) -> Option<String> {
let should_skip = context
.skip_context
.skip_macro(&context.snippet(mac.path.span).to_owned());
.skip_macro(context.snippet(mac.path.span));
if should_skip {
None
} else {

View File

@ -81,6 +81,7 @@ pub struct ModuleResolutionError {
pub(crate) kind: ModuleResolutionErrorKind,
}
/// Defines variants similar to those of [rustc_expand::module::ModError]
#[derive(Debug, Error)]
pub(crate) enum ModuleResolutionErrorKind {
/// Find a file that cannot be parsed.
@ -89,6 +90,12 @@ pub(crate) enum ModuleResolutionErrorKind {
/// File cannot be found.
#[error("{file} does not exist")]
NotFound { file: PathBuf },
/// File a.rs and a/mod.rs both exist
#[error("file for module found at both {default_path:?} and {secondary_path:?}")]
MultipleCandidates {
default_path: PathBuf,
secondary_path: PathBuf,
},
}
#[derive(Clone)]
@ -444,12 +451,31 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
}
Ok(Some(SubModKind::MultiExternal(mods_outside_ast)))
}
Err(_) => Err(ModuleResolutionError {
module: mod_name.to_string(),
kind: ModuleResolutionErrorKind::NotFound {
file: self.directory.path.clone(),
},
}),
Err(e) => match e {
ModError::FileNotFound(_, default_path, _secondary_path) => {
Err(ModuleResolutionError {
module: mod_name.to_string(),
kind: ModuleResolutionErrorKind::NotFound { file: default_path },
})
}
ModError::MultipleCandidates(_, default_path, secondary_path) => {
Err(ModuleResolutionError {
module: mod_name.to_string(),
kind: ModuleResolutionErrorKind::MultipleCandidates {
default_path,
secondary_path,
},
})
}
ModError::ParserError(_)
| ModError::CircularInclusion(_)
| ModError::ModInBlock(_) => Err(ModuleResolutionError {
module: mod_name.to_string(),
kind: ModuleResolutionErrorKind::ParseError {
file: self.directory.path.clone(),
},
}),
},
}
}
@ -458,6 +484,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
self.directory.path.push(path.as_str());
self.directory.ownership = DirectoryOwnership::Owned { relative: None };
} else {
let id = id.as_str();
// We have to push on the current module name in the case of relative
// paths in order to ensure that any additional module paths from inline
// `mod x { ... }` come after the relative extension.
@ -468,9 +495,15 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
if let Some(ident) = relative.take() {
// remove the relative offset
self.directory.path.push(ident.as_str());
// In the case where there is an x.rs and an ./x directory we want
// to prevent adding x twice. For example, ./x/x
if self.directory.path.exists() && !self.directory.path.join(id).exists() {
return;
}
}
}
self.directory.path.push(id.as_str());
self.directory.path.push(id);
}
}

View File

@ -26,8 +26,6 @@ use crate::spanned::Spanned;
use crate::types::{can_be_overflowed_type, SegmentParam};
use crate::utils::{count_newlines, extra_offset, first_line_width, last_line_width, mk_sp};
const SHORT_ITEM_THRESHOLD: usize = 10;
/// A list of `format!`-like macros, that take a long format string and a list of arguments to
/// format.
///
@ -572,7 +570,12 @@ impl<'a> Context<'a> {
if one_line {
tactic = DefinitiveListTactic::SpecialMacro(num_args_before);
};
} else if is_every_expr_simple(&self.items) && no_long_items(list_items) {
} else if is_every_expr_simple(&self.items)
&& no_long_items(
list_items,
self.context.config.short_array_element_width_threshold(),
)
{
tactic = DefinitiveListTactic::Mixed;
}
}
@ -755,9 +758,9 @@ fn shape_from_indent_style(
}
}
fn no_long_items(list: &[ListItem]) -> bool {
fn no_long_items(list: &[ListItem], short_array_element_width_threshold: usize) -> bool {
list.iter()
.all(|item| item.inner_as_ref().len() <= SHORT_ITEM_THRESHOLD)
.all(|item| item.inner_as_ref().len() <= short_array_element_width_threshold)
}
/// In case special-case style is required, returns an offset from which we start horizontal layout.

View File

@ -12,6 +12,7 @@ use rustc_span::{
use crate::config::file_lines::LineRange;
use crate::ignore_path::IgnorePathSet;
use crate::parse::parser::{ModError, ModulePathSuccess};
use crate::source_map::LineRangeUtils;
use crate::utils::starts_with_newline;
use crate::visitor::SnippetProvider;
@ -145,13 +146,33 @@ impl ParseSess {
})
}
/// Determine the submodule path for the given module identifier.
///
/// * `id` - The name of the module
/// * `relative` - If Some(symbol), the symbol name is a directory relative to the dir_path.
/// If relative is Some, resolve the submodle at {dir_path}/{symbol}/{id}.rs
/// or {dir_path}/{symbol}/{id}/mod.rs. if None, resolve the module at {dir_path}/{id}.rs.
/// * `dir_path` - Module resolution will occur relative to this direcotry.
pub(crate) fn default_submod_path(
&self,
id: symbol::Ident,
relative: Option<symbol::Ident>,
dir_path: &Path,
) -> Result<rustc_expand::module::ModulePathSuccess, rustc_expand::module::ModError<'_>> {
rustc_expand::module::default_submod_path(&self.parse_sess, id, relative, dir_path)
) -> Result<ModulePathSuccess, ModError<'_>> {
rustc_expand::module::default_submod_path(&self.parse_sess, id, relative, dir_path).or_else(
|e| {
// If resloving a module relative to {dir_path}/{symbol} fails because a file
// could not be found, then try to resolve the module relative to {dir_path}.
// If we still can't find the module after searching for it in {dir_path},
// surface the original error.
if matches!(e, ModError::FileNotFound(..)) && relative.is_some() {
rustc_expand::module::default_submod_path(&self.parse_sess, id, None, dir_path)
.map_err(|_| e)
} else {
Err(e)
}
},
)
}
pub(crate) fn is_file_parsed(&self, path: &Path) -> bool {
@ -197,6 +218,15 @@ impl ParseSess {
self.parse_sess.source_map().lookup_char_pos(pos).line
}
// TODO(calebcartwright): Preemptive, currently unused addition
// that will be used to support formatting scenarios that take original
// positions into account
/// Determines whether two byte positions are in the same source line.
#[allow(dead_code)]
pub(crate) fn byte_pos_same_line(&self, a: BytePos, b: BytePos) -> bool {
self.line_of_byte_pos(a) == self.line_of_byte_pos(b)
}
pub(crate) fn span_to_debug_info(&self, span: Span) -> String {
self.parse_sess.source_map().span_to_diagnostic_string(span)
}

View File

@ -113,17 +113,10 @@ impl Spanned for ast::Param {
impl Spanned for ast::GenericParam {
fn span(&self) -> Span {
let lo = if let ast::GenericParamKind::Const {
ty: _,
kw_span,
default: _,
} = self.kind
{
kw_span.lo()
} else if self.attrs.is_empty() {
self.ident.span.lo()
} else {
self.attrs[0].span.lo()
let lo = match self.kind {
_ if !self.attrs.is_empty() => self.attrs[0].span.lo(),
ast::GenericParamKind::Const { kw_span, .. } => kw_span.lo(),
_ => self.ident.span.lo(),
};
let hi = if self.bounds.is_empty() {
self.ident.span.hi()

View File

@ -278,6 +278,9 @@ fn break_string(max_width: usize, trim_end: bool, line_end: &str, input: &[&str]
}
cur_index
};
if max_width_index_in_input == 0 {
return SnippetState::EndOfInput(input.concat());
}
// Find the position in input for breaking the string
if line_end.is_empty()
@ -301,7 +304,7 @@ fn break_string(max_width: usize, trim_end: bool, line_end: &str, input: &[&str]
return if trim_end {
SnippetState::LineEnd(input[..=url_index_end].concat(), index_plus_ws + 1)
} else {
return SnippetState::LineEnd(input[..=index_plus_ws].concat(), index_plus_ws + 1);
SnippetState::LineEnd(input[..=index_plus_ws].concat(), index_plus_ws + 1)
};
}

View File

@ -290,3 +290,33 @@ fn get_code_blocks() -> Vec<ConfigCodeBlock> {
code_blocks
}
#[test]
fn check_unstable_option_tracking_issue_numbers() {
// Ensure that tracking issue links point to the correct issue number
let tracking_issue =
regex::Regex::new(r"\(tracking issue: \[#(?P<number>\d+)\]\((?P<link>\S+)\)\)")
.expect("failed creating configuration pattern");
let lines = BufReader::new(
fs::File::open(Path::new(CONFIGURATIONS_FILE_NAME))
.unwrap_or_else(|_| panic!("couldn't read file {}", CONFIGURATIONS_FILE_NAME)),
)
.lines()
.map(Result::unwrap)
.enumerate();
for (idx, line) in lines {
if let Some(capture) = tracking_issue.captures(&line) {
let number = capture.name("number").unwrap().as_str();
let link = capture.name("link").unwrap().as_str();
assert!(
link.ends_with(number),
"{} on line {} does not point to issue #{}",
link,
idx + 1,
number,
);
}
}
}

View File

@ -307,6 +307,52 @@ fn assert_output(source: &Path, expected_filename: &Path) {
}
}
// Helper function for comparing the results of rustfmt
// to a known output generated by one of the write modes.
fn assert_stdin_output(
source: &Path,
expected_filename: &Path,
emit_mode: EmitMode,
has_diff: bool,
) {
let mut config = Config::default();
config.set().newline_style(NewlineStyle::Unix);
config.set().emit_mode(emit_mode);
let mut source_file = fs::File::open(&source).expect("couldn't open source");
let mut source_text = String::new();
source_file
.read_to_string(&mut source_text)
.expect("Failed reading target");
let input = Input::Text(source_text);
// Populate output by writing to a vec.
let mut buf: Vec<u8> = vec![];
{
let mut session = Session::new(config, Some(&mut buf));
session.format(input).unwrap();
let errors = ReportedErrors {
has_diff: has_diff,
..Default::default()
};
assert_eq!(session.errors, errors);
}
let mut expected_file = fs::File::open(&expected_filename).expect("couldn't open target");
let mut expected_text = String::new();
expected_file
.read_to_string(&mut expected_text)
.expect("Failed reading target");
let output = String::from_utf8(buf).unwrap();
let compare = make_diff(&expected_text, &output, DIFF_CONTEXT_SIZE);
if !compare.is_empty() {
let mut failures = HashMap::new();
failures.insert(source.to_owned(), compare);
print_mismatches_default_message(failures);
panic!("Text does not match expected output");
}
}
// Idempotence tests. Files in tests/target are checked to be unaltered by
// rustfmt.
#[nightly_only_test]
@ -329,43 +375,21 @@ fn idempotence_tests() {
});
}
// Run rustfmt on itself. This operation must be idempotent. We also check that
// no warnings are emitted.
// Issue-3443: these tests require nightly
#[nightly_only_test]
#[test]
fn self_tests() {
init_log();
let mut files = get_test_files(Path::new("tests"), false);
let bin_directories = vec!["cargo-fmt", "git-rustfmt", "bin", "format-diff"];
for dir in bin_directories {
let mut path = PathBuf::from("src");
path.push(dir);
path.push("main.rs");
files.push(path);
}
files.push(PathBuf::from("src/lib.rs"));
let (reports, count, fails) = check_files(files, &Some(PathBuf::from("rustfmt.toml")));
let mut warnings = 0;
// Display results.
println!("Ran {} self tests.", count);
assert_eq!(fails, 0, "{} self tests failed", fails);
for format_report in reports {
println!(
"{}",
FormatReportFormatterBuilder::new(&format_report).build()
);
warnings += format_report.warning_count();
}
assert_eq!(
warnings, 0,
"Rustfmt's code generated {} warnings",
warnings
);
let get_exe_path = |name| {
let mut path = env::current_exe().unwrap();
path.pop();
path.set_file_name(format!("{name}{}", env::consts::EXE_SUFFIX));
path
};
let status = Command::new(get_exe_path("cargo-fmt"))
.args(["--check", "--all"])
.env("RUSTFMT", get_exe_path("rustfmt"))
.status()
.unwrap();
assert!(status.success());
}
#[test]
@ -420,9 +444,9 @@ fn stdin_formatting_smoke_test() {
}
#[cfg(not(windows))]
assert_eq!(buf, "stdin:\n\nfn main() {}\n".as_bytes());
assert_eq!(buf, "<stdin>:\n\nfn main() {}\n".as_bytes());
#[cfg(windows)]
assert_eq!(buf, "stdin:\n\nfn main() {}\r\n".as_bytes());
assert_eq!(buf, "<stdin>:\n\nfn main() {}\r\n".as_bytes());
}
#[test]
@ -463,6 +487,30 @@ fn stdin_works_with_modified_lines() {
assert_eq!(buf, output.as_bytes());
}
/// Ensures that `EmitMode::Json` works with input from `stdin`.
#[test]
fn stdin_works_with_json() {
init_log();
assert_stdin_output(
Path::new("tests/writemode/source/stdin.rs"),
Path::new("tests/writemode/target/stdin.json"),
EmitMode::Json,
true,
);
}
/// Ensures that `EmitMode::Checkstyle` works with input from `stdin`.
#[test]
fn stdin_works_with_checkstyle() {
init_log();
assert_stdin_output(
Path::new("tests/writemode/source/stdin.rs"),
Path::new("tests/writemode/target/stdin.xml"),
EmitMode::Checkstyle,
false,
);
}
#[test]
fn stdin_disable_all_formatting_test() {
init_log();
@ -502,7 +550,10 @@ fn stdin_generated_files_issue_5172() {
assert!(session.has_no_errors());
}
// N.B. this should be changed once `format_generated_files` is supported with stdin
assert_eq!(buf, "stdin:\n\n//@generated\nfn main() {}\n".as_bytes());
assert_eq!(
String::from_utf8(buf).unwrap(),
"<stdin>:\n\n//@generated\nfn main() {}\n",
);
}
#[test]
@ -914,3 +965,52 @@ fn verify_check_works() {
.status()
.expect("run with check option failed");
}
#[test]
fn verify_check_works_with_stdin() {
init_log();
let mut child = Command::new(rustfmt().to_str().unwrap())
.arg("--check")
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("run with check option failed");
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
stdin
.write_all("fn main() {}\n".as_bytes())
.expect("Failed to write to rustfmt --check");
}
let output = child
.wait_with_output()
.expect("Failed to wait on rustfmt child");
assert!(output.status.success());
}
#[test]
fn verify_check_l_works_with_stdin() {
init_log();
let mut child = Command::new(rustfmt().to_str().unwrap())
.arg("--check")
.arg("-l")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("run with check option failed");
{
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
stdin
.write_all("fn main()\n{}\n".as_bytes())
.expect("Failed to write to rustfmt --check");
}
let output = child
.wait_with_output()
.expect("Failed to wait on rustfmt child");
assert!(output.status.success());
assert_eq!(std::str::from_utf8(&output.stdout).unwrap(), "<stdin>\n");
}

View File

@ -50,3 +50,33 @@ fn skip_out_of_line_nested_inline_within_out_of_line() {
&["tests/mod-resolver/skip-files-issue-5065/one.rs"],
);
}
#[test]
fn fmt_out_of_line_test_modules() {
// See also https://github.com/rust-lang/rustfmt/issues/5119
verify_mod_resolution(
"tests/mod-resolver/test-submodule-issue-5119/tests/test1.rs",
&[
"tests/mod-resolver/test-submodule-issue-5119/tests/test1.rs",
"tests/mod-resolver/test-submodule-issue-5119/tests/test1/sub1.rs",
"tests/mod-resolver/test-submodule-issue-5119/tests/test1/sub2.rs",
"tests/mod-resolver/test-submodule-issue-5119/tests/test1/sub3/sub4.rs",
],
)
}
#[test]
fn fallback_and_try_to_resolve_external_submod_relative_to_current_dir_path() {
// See also https://github.com/rust-lang/rustfmt/issues/5198
verify_mod_resolution(
"tests/mod-resolver/issue-5198/lib.rs",
&[
"tests/mod-resolver/issue-5198/a.rs",
"tests/mod-resolver/issue-5198/lib/b.rs",
"tests/mod-resolver/issue-5198/lib/c/mod.rs",
"tests/mod-resolver/issue-5198/lib/c/e.rs",
"tests/mod-resolver/issue-5198/lib/c/d/f.rs",
"tests/mod-resolver/issue-5198/lib/c/d/g/mod.rs",
],
)
}

View File

@ -575,7 +575,16 @@ impl Rewrite for ast::GenericParam {
let mut result = String::with_capacity(128);
// FIXME: If there are more than one attributes, this will force multiline.
match self.attrs.rewrite(context, shape) {
Some(ref rw) if !rw.is_empty() => result.push_str(&format!("{} ", rw)),
Some(ref rw) if !rw.is_empty() => {
result.push_str(rw);
// When rewriting generic params, an extra newline should be put
// if the attributes end with a doc comment
if let Some(true) = self.attrs.last().map(|a| a.is_doc_comment()) {
result.push_str(&shape.indent.to_string_with_newline(context.config));
} else {
result.push(' ');
}
}
_ => (),
}

View File

@ -646,9 +646,22 @@ pub(crate) fn trim_left_preserve_layout(
}
/// Based on the given line, determine if the next line can be indented or not.
/// This allows to preserve the indentation of multi-line literals.
pub(crate) fn indent_next_line(kind: FullCodeCharKind, _line: &str, config: &Config) -> bool {
!(kind.is_string() || (config.version() == Version::Two && kind.is_commented_string()))
/// This allows to preserve the indentation of multi-line literals when
/// re-inserted a code block that has been formatted separately from the rest
/// of the code, such as code in macro defs or code blocks doc comments.
pub(crate) fn indent_next_line(kind: FullCodeCharKind, line: &str, config: &Config) -> bool {
if kind.is_string() {
// If the string ends with '\', the string has been wrapped over
// multiple lines. If `format_strings = true`, then the indentation of
// strings wrapped over multiple lines will have been adjusted while
// formatting the code block, therefore the string's indentation needs
// to be adjusted for the code surrounding the code block.
config.format_strings() && line.ends_with('\\')
} else if config.version() == Version::Two {
!kind.is_commented_string()
} else {
true
}
}
pub(crate) fn is_empty_line(s: &str) -> bool {

View File

@ -160,8 +160,18 @@ pub(crate) fn rewrite_with_alignment<T: AlignedItem>(
};
let init_span = mk_sp(span.lo(), init_last_pos);
let one_line_width = if rest.is_empty() { one_line_width } else { 0 };
let result =
rewrite_aligned_items_inner(context, init, init_span, shape.indent, one_line_width)?;
// if another group follows, we must force a separator
let force_separator = !rest.is_empty();
let result = rewrite_aligned_items_inner(
context,
init,
init_span,
shape.indent,
one_line_width,
force_separator,
)?;
if rest.is_empty() {
Some(result + spaces)
} else {
@ -201,6 +211,7 @@ fn rewrite_aligned_items_inner<T: AlignedItem>(
span: Span,
offset: Indent,
one_line_width: usize,
force_trailing_separator: bool,
) -> Option<String> {
// 1 = ","
let item_shape = Shape::indented(offset, context.config).sub_width(1)?;
@ -246,9 +257,15 @@ fn rewrite_aligned_items_inner<T: AlignedItem>(
});
}
let separator_tactic = if force_trailing_separator {
SeparatorTactic::Always
} else {
context.config.trailing_comma()
};
let fmt = ListFormatting::new(item_shape, context.config)
.tactic(tactic)
.trailing_separator(context.config.trailing_comma())
.trailing_separator(separator_tactic)
.preserve_newline(true);
write_list(&items, &fmt)
}

View File

@ -1,6 +1,7 @@
// Integration tests for cargo-fmt.
use std::env;
use std::path::Path;
use std::process::Command;
/// Run the cargo-fmt executable and return its output.
@ -71,3 +72,26 @@ fn rustfmt_help() {
assert_that!(&["--", "-h"], contains("Format Rust code"));
assert_that!(&["--", "--help=config"], contains("Configuration Options:"));
}
#[test]
fn cargo_fmt_out_of_line_test_modules() {
// See also https://github.com/rust-lang/rustfmt/issues/5119
let expected_modified_files = [
"tests/mod-resolver/test-submodule-issue-5119/src/lib.rs",
"tests/mod-resolver/test-submodule-issue-5119/tests/test1.rs",
"tests/mod-resolver/test-submodule-issue-5119/tests/test1/sub1.rs",
"tests/mod-resolver/test-submodule-issue-5119/tests/test1/sub2.rs",
"tests/mod-resolver/test-submodule-issue-5119/tests/test1/sub3/sub4.rs",
];
let args = [
"-v",
"--check",
"--manifest-path",
"tests/mod-resolver/test-submodule-issue-5119/Cargo.toml",
];
let (stdout, _) = cargo_fmt(&args);
for file in expected_modified_files {
let path = Path::new(file).canonicalize().unwrap();
assert!(stdout.contains(&format!("Diff in {}", path.display())))
}
}

View File

View File

@ -0,0 +1 @@
mod a;

View File

@ -0,0 +1 @@
fn main( ) { println!("Hello World!") }

View File

@ -0,0 +1,3 @@
mod a;
mod b;
mod c;

View File

@ -0,0 +1 @@
fn main( ) { println!("Hello World!") }

View File

@ -0,0 +1,3 @@
mod e;
mod f;
mod g;

View File

@ -0,0 +1,16 @@
This file is contained in the './lib/c/d/' directory.
The directory name './lib/c/d/' conflicts with the './lib/c/d.rs' file name.
'./lib/c/d.rs' defines 3 external modules:
* mod e;
* mod f;
* mod g;
Module resolution will fail if we look for './lib/c/d/e.rs' or './lib/c/d/e/mod.rs',
so we should fall back to looking for './lib/c/e.rs', which correctly finds the modlue, that
rustfmt should format.
'./lib/c/d/f.rs' and './lib/c/d/g/mod.rs' exist at the default submodule paths so we should be able
to resolve these modules with no problems.

View File

@ -0,0 +1 @@
fn main( ) { println!("Hello World!") }

View File

@ -0,0 +1 @@
fn main( ) { println!("Hello World!") }

View File

@ -0,0 +1 @@
fn main( ) { println!("Hello World!") }

View File

@ -0,0 +1,3 @@
mod d;
fn main( ) { println!("Hello World!") }

View File

@ -0,0 +1,16 @@
This file is contained in the './lib' directory.
The directory name './lib' conflicts with the './lib.rs' file name.
'lib.rs' defines 3 external modules:
* mod a;
* mod b;
* mod c;
Module resolution will fail if we look for './lib/a.rs' or './lib/a/mod.rs',
so we should fall back to looking for './a.rs', which correctly finds the modlue that
rustfmt should format.
'./lib/b.rs' and './lib/c/mod.rs' exist at the default submodule paths so we should be able
to resolve these modules with no problems.

View File

@ -0,0 +1,3 @@
// module resolution fails because the path does not exist.
#[path = "path/to/does_not_exist.rs"]
mod a;

View File

@ -0,0 +1,2 @@
// module resolution fails because `./a/b.rs` does not exist
mod b;

View File

@ -0,0 +1 @@
mod a;

View File

@ -0,0 +1,2 @@
// module resolution fails because `./a.rs` does not exist
mod a;

View File

@ -0,0 +1,8 @@
[package]
name = "rustfmt-test-submodule-issue"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,7 @@
pub fn foo() -> i32 {
3
}
pub fn bar() -> i32 {
4
}

View File

@ -0,0 +1,8 @@
mod test1 {
#[cfg(unix)]
mod sub1;
#[cfg(not(unix))]
mod sub2;
mod sub3;
}

View File

@ -0,0 +1,6 @@
use rustfmt_test_submodule_issue::foo;
#[test]
fn test_foo() {
assert_eq!(3, foo());
}

View File

@ -0,0 +1,6 @@
use rustfmt_test_submodule_issue::bar;
#[test]
fn test_bar() {
assert_eq!(4, bar());
}

View File

@ -0,0 +1 @@
mod sub4;

View File

@ -106,3 +106,54 @@ fn inline_config() {
&& contains("format_strings = true")
);
}
#[test]
fn rustfmt_usage_text() {
let args = ["--help"];
let (stdout, _) = rustfmt(&args);
assert!(stdout.contains("Format Rust code\n\nusage: rustfmt [options] <file>..."));
}
#[test]
fn mod_resolution_error_multiple_candidate_files() {
// See also https://github.com/rust-lang/rustfmt/issues/5167
let default_path = Path::new("tests/mod-resolver/issue-5167/src/a.rs");
let secondary_path = Path::new("tests/mod-resolver/issue-5167/src/a/mod.rs");
let error_message = format!(
"file for module found at both {:?} and {:?}",
default_path.canonicalize().unwrap(),
secondary_path.canonicalize().unwrap(),
);
let args = ["tests/mod-resolver/issue-5167/src/lib.rs"];
let (_stdout, stderr) = rustfmt(&args);
assert!(stderr.contains(&error_message))
}
#[test]
fn mod_resolution_error_sibling_module_not_found() {
let args = ["tests/mod-resolver/module-not-found/sibling_module/lib.rs"];
let (_stdout, stderr) = rustfmt(&args);
// Module resolution fails because we're unable to find `a.rs` in the same directory as lib.rs
assert!(stderr.contains("a.rs does not exist"))
}
#[test]
fn mod_resolution_error_relative_module_not_found() {
let args = ["tests/mod-resolver/module-not-found/relative_module/lib.rs"];
let (_stdout, stderr) = rustfmt(&args);
// The file `./a.rs` and directory `./a` both exist.
// Module resolution fails becuase we're unable to find `./a/b.rs`
#[cfg(not(windows))]
assert!(stderr.contains("a/b.rs does not exist"));
#[cfg(windows)]
assert!(stderr.contains("a\\b.rs does not exist"));
}
#[test]
fn mod_resolution_error_path_attribute_does_not_exist() {
let args = ["tests/mod-resolver/module-not-found/bad_path_attribute/lib.rs"];
let (_stdout, stderr) = rustfmt(&args);
// The path attribute points to a file that does not exist
assert!(stderr.contains("does_not_exist.rs does not exist"));
}

View File

@ -0,0 +1,14 @@
// rustfmt-imports_granularity: Crate
use foo::a;
use foo::a;
use foo::b;
use foo::b as b2;
use foo::b::f;
use foo::b::g;
use foo::b::g as g2;
use foo::c;
use foo::d::e;
use qux::h;
use qux::h as h2;
use qux::i;

View File

@ -0,0 +1,33 @@
// rustfmt-imports_granularity: Module
#![allow(dead_code)]
mod a {
pub mod b {
pub struct Data {
pub a: i32,
}
}
use crate::a::b::Data;
use crate::a::b::Data as Data2;
pub fn data(a: i32) -> Data {
Data { a }
}
pub fn data2(a: i32) -> Data2 {
Data2 { a }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test() {
data(1);
data2(1);
}
}
}

15
tests/source/5131_one.rs Normal file
View File

@ -0,0 +1,15 @@
// rustfmt-imports_granularity: One
pub use foo::x;
pub use foo::x as x2;
pub use foo::y;
use bar::a;
use bar::b;
use bar::b::f;
use bar::b::f as f2;
use bar::b::g;
use bar::c;
use bar::d::e;
use bar::d::e as e2;
use qux::h;
use qux::i;

View File

@ -0,0 +1,11 @@
// rustfmt-short_array_element_width_threshold: 10
fn main() {
pub const FORMAT_TEST: [u64; 5] = [
0x0000000000000000,
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
0xcccccccccccccccc,
0xdddddddddddddddd,
];
}

View File

@ -0,0 +1,11 @@
// rustfmt-short_array_element_width_threshold: 20
fn main() {
pub const FORMAT_TEST: [u64; 5] = [
0x0000000000000000,
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
0xcccccccccccccccc,
0xdddddddddddddddd,
];
}

View File

@ -0,0 +1,12 @@
// rustfmt-max_width: 20
// rustfmt-short_array_element_width_threshold: 30
fn main() {
pub const FORMAT_TEST: [u64; 5] = [
0x0000000000000000,
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
0xcccccccccccccccc,
0xdddddddddddddddd,
];
}

View File

@ -4,6 +4,7 @@ use a::{b::c, d::e};
use a::{f, g::{h, i}};
use a::{j::{self, k::{self, l}, m}, n::{o::p, q}};
pub use a::{r::s, t};
use b::{c::d, self};
#[cfg(test)]
use foo::{a::b, c::d};

View File

@ -0,0 +1,11 @@
// rustfmt-format_strings: true
macro_rules! test {
() => {
fn from() {
None.expect(
"We asserted that `buffer.len()` is exactly `$n` so we can expect `ApInt::from_iter` to be successful.",
)
}
};
}

View File

@ -0,0 +1,12 @@
// rustfmt-format_strings: true
// rustfmt-hard_tabs: true
macro_rules! test {
() => {
fn from() {
None.expect(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
)
}
};
}

View File

@ -0,0 +1,11 @@
// rustfmt-format_strings: true
macro_rules! test {
() => {
fn from() {
None.expect(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
)
}
};
}

View File

@ -0,0 +1,14 @@
// rustfmt-struct_field_align_threshold: 30
// rustfmt-trailing_comma: Never
struct Foo {
group_a: u8,
group_b: u8,
}
struct Bar {
group_a: u8,
group_b: u8
}

View File

@ -0,0 +1,14 @@
// rustfmt-struct_field_align_threshold: 30
// rustfmt-trailing_comma: Always
struct Foo {
group_a: u8,
group_b: u8
}
struct Bar {
group_a: u8,
group_b: u8,
}

View File

@ -0,0 +1,22 @@
// rustfmt-wrap_comments: true
/// A comment to test special unicode characters on boundaries
/// 是,是,是,是,是,是,是,是,是,是,是,是 it should break right here this goes to the next line
fn main() {
if xxx {
let xxx = xxx
.into_iter()
.filter(|(xxx, xxx)| {
if let Some(x) = Some(1) {
// xxxxxxxxxxxxxxxxxx, xxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxx xxx xxxxxxx, xxxxx xxx
// xxxxxxxxxx. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxx xxxxxxx
// 是sdfadsdfxxxxxxxxxsdfaxxxxxx_xxxxx_masdfaonxxx
if false {
return true;
}
}
false
})
.collect();
}
}

View File

@ -0,0 +1,24 @@
fn main() {
// 5042 deals with trailing commas, not the indentation issue of these comments
// When a future PR fixes the inentation issues these test can be updated
let _ = std::ops::Add::add(10, 20
// ...
// ...,
);
let _ = std::ops::Add::add(10, 20
/* ... */
// ...,
);
let _ = std::ops::Add::add(10, 20
// ...,
// ...,
);
let _ = std::ops::Add::add(10, 20
// ...,
/* ...
*/,
);
}

View File

@ -0,0 +1,24 @@
fn main() {
// 5042 deals with trailing commas, not the indentation issue of these comments
// When a future PR fixes the inentation issues these test can be updated
let _ = std::ops::Add::add(10, 20
// ...
// ...
);
let _ = std::ops::Add::add(10, 20
/* ... */
// ...
);
let _ = std::ops::Add::add(10, 20
// ...
// ...
);
let _ = std::ops::Add::add(10, 20
// ...
/* ...
*/
);
}

View File

@ -0,0 +1,9 @@
fn main() {
let _ = std::ops::Add::add(10, 20
// ...,
);
let _ = std::ops::Add::add(10, 20
/* ... */,
);
}

View File

@ -0,0 +1,10 @@
fn main() {
let _ = std::ops::Add::add(10, 20
// ...
);
let _ = std::ops::Add::add(10, 20
/* ... */
);
}

View File

@ -0,0 +1,4 @@
// rustfmt-wrap_comments: true
/// > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
fn block_quote() {}

View File

@ -0,0 +1,10 @@
// rustfmt-wrap_comments: true
/// > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
///
/// > > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
///
/// > > > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
///
/// > > > > > > > > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
fn block_quote() {}

View File

@ -0,0 +1,4 @@
// rustfmt-wrap_comments: true
/// > For each sample received, the middleware internally maintains a sample_state relative to each DataReader. The sample_state can either be READ or NOT_READ.
fn block_quote() {}

View File

@ -0,0 +1,11 @@
// rustfmt-wrap_comments: false
/// no markdown header so rustfmt should wrap this comment when `format_code_in_doc_comments = true` and `wrap_comments = true`
fn not_documented_with_markdown_header() {
// This is just a normal inline comment so rustfmt should wrap this comment when `wrap_comments = true`
}
/// # We're using a markdown header here so rustfmt should refuse to wrap this comment in all circumstances
fn documented_with_markdown_header() {
// # We're using a markdown header in an inline comment. rustfmt should be able to wrap this comment when `wrap_comments = true`
}

View File

@ -0,0 +1,11 @@
// rustfmt-wrap_comments: true
/// no markdown header so rustfmt should wrap this comment when `format_code_in_doc_comments = true` and `wrap_comments = true`
fn not_documented_with_markdown_header() {
// This is just a normal inline comment so rustfmt should wrap this comment when `wrap_comments = true`
}
/// # We're using a markdown header here so rustfmt should refuse to wrap this comment in all circumstances
fn documented_with_markdown_header() {
// # We're using a markdown header in an inline comment. rustfmt should be able to wrap this comment when `wrap_comments = true`
}

View File

@ -0,0 +1,62 @@
// rustfmt-merge_derives:true
#[rustfmt::skip::attributes(derive)]
#[allow(dead_code)]
#[derive(StructField)]
#[derive(Clone)]
struct DoNotMergeDerives {
field: String,
}
#[allow(dead_code)]
#[derive(StructField)]
#[rustfmt::skip::attributes(derive)]
#[derive(Clone)]
struct DoNotMergeDerivesSkipInMiddle {
field: String,
}
#[allow(dead_code)]
#[derive(StructField)]
#[derive(Clone)]
#[rustfmt::skip::attributes(derive)]
struct DoNotMergeDerivesSkipAtEnd {
field: String,
}
#[allow(dead_code)]
#[derive(StructField)]
#[derive(Clone)]
struct MergeDerives {
field: String,
}
mod inner_attribute_derive_skip {
#![rustfmt::skip::attributes(derive)]
#[allow(dead_code)]
#[derive(StructField)]
#[derive(Clone)]
struct DoNotMergeDerives {
field: String,
}
}
#[rustfmt::skip::attributes(derive)]
mod outer_attribute_derive_skip {
#[allow(dead_code)]
#[derive(StructField)]
#[derive(Clone)]
struct DoNotMergeDerives {
field: String,
}
}
mod no_derive_skip {
#[allow(dead_code)]
#[derive(StructField)]
#[derive(Clone)]
struct MergeDerives {
field: String,
}
}

113
tests/source/issue_4854.rs Normal file
View File

@ -0,0 +1,113 @@
struct Struct {
// Multiline comment
// should be formatted
// properly.
}
struct Struct2 {
// This formatting
// Should be changed
}
struct Struct3(
// This
// is
// correct
);
struct Struct4(
// This
// is
// not
// correct
);
struct Struct5 {
/*
Comment block
with many lines.
*/
}
struct Struct6(
/*
Comment block
with many lines.
*/
);
struct Struct7 {
/*
Invalid
format
*/
}
struct Struct8(
/*
Invalid
format
*/
);
struct Struct9 { /* bar */ }
struct Struct10 { /* bar
baz
*/ }
mod module {
struct Struct {
// Multiline comment
// should be formatted
// properly.
}
struct Struct2 {
// This formatting
// Should be changed
}
struct Struct3(
// This
// is
// correct
);
struct Struct4(
// This
// is
// not
// correct
);
struct Struct5 {
/*
Comment block
with many lines.
*/
}
struct Struct6(
/*
Comment block
with many lines.
*/
);
struct Struct7 {
/*
Invalid
format
*/
}
struct Struct8(
/*
Invalid
format
*/
);
struct Struct9 { /* bar */ }
}

View File

@ -0,0 +1,9 @@
// rustfmt-imports_granularity: Crate
use foo::{
a, b, b as b2,
b::{f, g, g as g2},
c,
d::e,
};
use qux::{h, h as h2, i};

View File

@ -0,0 +1,32 @@
// rustfmt-imports_granularity: Module
#![allow(dead_code)]
mod a {
pub mod b {
pub struct Data {
pub a: i32,
}
}
use crate::a::b::{Data, Data as Data2};
pub fn data(a: i32) -> Data {
Data { a }
}
pub fn data2(a: i32) -> Data2 {
Data2 { a }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test() {
data(1);
data2(1);
}
}
}

12
tests/target/5131_one.rs Normal file
View File

@ -0,0 +1,12 @@
// rustfmt-imports_granularity: One
pub use foo::{x, x as x2, y};
use {
bar::{
a,
b::{self, f, g},
c,
d::{e, e as e2},
},
qux::{h, i},
};

View File

@ -25,9 +25,8 @@ pub enum E {
}
pub enum E2 {
// This can be changed once https://github.com/rust-lang/rustfmt/issues/4854 is fixed
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
}
pub struct S {
@ -42,9 +41,8 @@ pub struct S {
}
pub struct S2 {
// This can be changed once https://github.com/rust-lang/rustfmt/issues/4854 is fixed
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
}
fn foo(

View File

@ -13,9 +13,8 @@ pub enum E {
}
pub enum E2 {
// This can be changed once https://github.com/rust-lang/rustfmt/issues/4854 is fixed
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
}
pub struct S {
@ -30,9 +29,8 @@ pub struct S {
}
pub struct S2 {
// This can be changed once https://github.com/rust-lang/rustfmt/issues/4854 is fixed
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
}
fn foo(

View File

@ -14,8 +14,8 @@ pub enum E {
pub enum E2 {
// This can be changed once https://github.com/rust-lang/rustfmt/issues/4854 is fixed
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
}
pub enum E3 {
@ -42,8 +42,8 @@ pub struct S {
pub struct S2 {
// This can be changed once https://github.com/rust-lang/rustfmt/issues/4854 is fixed
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
}
pub struct S3 {

View File

@ -15,8 +15,8 @@ pub enum E {
pub enum E2 {
// This can be changed once https://github.com/rust-lang/rustfmt/issues/4854 is fixed
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
}
pub enum E3 {
@ -43,8 +43,8 @@ pub struct S {
pub struct S2 {
// This can be changed once https://github.com/rust-lang/rustfmt/issues/4854 is fixed
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
// Expand as needed, numbers should be ascending according to the stage
// through the inclusion pipeline, or according to the descriptions
}
pub struct S3 {

View File

@ -0,0 +1,11 @@
// rustfmt-short_array_element_width_threshold: 10
fn main() {
pub const FORMAT_TEST: [u64; 5] = [
0x0000000000000000,
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
0xcccccccccccccccc,
0xdddddddddddddddd,
];
}

View File

@ -0,0 +1,8 @@
// rustfmt-short_array_element_width_threshold: 20
fn main() {
pub const FORMAT_TEST: [u64; 5] = [
0x0000000000000000, 0xaaaaaaaaaaaaaaaa, 0xbbbbbbbbbbbbbbbb, 0xcccccccccccccccc,
0xdddddddddddddddd,
];
}

View File

@ -0,0 +1,12 @@
// rustfmt-max_width: 20
// rustfmt-short_array_element_width_threshold: 30
fn main() {
pub const FORMAT_TEST: [u64; 5] = [
0x0000000000000000,
0xaaaaaaaaaaaaaaaa,
0xbbbbbbbbbbbbbbbb,
0xcccccccccccccccc,
0xdddddddddddddddd,
];
}

View File

@ -0,0 +1,14 @@
// Non-doc pre-comment of Foo
/// doc of Foo
// Non-doc post-comment of Foo
struct Foo<
// Non-doc pre-comment of 'a
/// doc of 'a
'a,
// Non-doc pre-comment of T
/// doc of T
T,
// Non-doc pre-comment of N
/// doc of N
const N: item,
>;

View File

@ -10,6 +10,8 @@ use a::n::o::p;
use a::n::q;
pub use a::r::s;
pub use a::t;
use b::c::d;
use b::{self};
use foo::e;
#[cfg(test)]

View File

@ -0,0 +1,12 @@
// rustfmt-format_strings: true
macro_rules! test {
() => {
fn from() {
None.expect(
"We asserted that `buffer.len()` is exactly `$n` so we can expect \
`ApInt::from_iter` to be successful.",
)
}
};
}

View File

@ -0,0 +1,17 @@
// rustfmt-format_strings: true
// rustfmt-hard_tabs: true
macro_rules! test {
() => {
fn from() {
None.expect(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis \
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu \
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in \
culpa qui officia deserunt mollit anim id est laborum.",
)
}
};
}

View File

@ -0,0 +1,16 @@
// rustfmt-format_strings: true
macro_rules! test {
() => {
fn from() {
None.expect(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis \
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. \
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu \
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in \
culpa qui officia deserunt mollit anim id est laborum.",
)
}
};
}

View File

@ -0,0 +1,14 @@
// rustfmt-struct_field_align_threshold: 30
// rustfmt-trailing_comma: Never
struct Foo {
group_a: u8,
group_b: u8
}
struct Bar {
group_a: u8,
group_b: u8
}

View File

@ -0,0 +1,70 @@
// rustfmt-brace_style: SameLineWhere
// rustfmt-comment_width: 100
// rustfmt-edition: 2018
// rustfmt-fn_args_layout: Compressed
// rustfmt-hard_tabs: false
// rustfmt-match_block_trailing_comma: true
// rustfmt-max_width: 100
// rustfmt-merge_derives: false
// rustfmt-newline_style: Unix
// rustfmt-normalize_doc_attributes: true
// rustfmt-overflow_delimited_expr: true
// rustfmt-reorder_imports: false
// rustfmt-reorder_modules: true
// rustfmt-struct_field_align_threshold: 20
// rustfmt-tab_spaces: 4
// rustfmt-trailing_comma: Never
// rustfmt-use_small_heuristics: Max
// rustfmt-use_try_shorthand: true
// rustfmt-wrap_comments: true
/// Lorem ipsum dolor sit amet.
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub struct BufferAttr {
/* NOTE: Blah blah blah blah blah. */
/// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt
/// ut labore et dolore magna aliqua. Morbi quis commodo odio aenean sed adipiscing. Nunc
/// congue nisi vitae suscipit tellus mauris a. Consectetur adipiscing elit pellentesque
/// habitant morbi tristique senectus.
pub foo: u32,
/// Elit eget gravida cum sociis natoque penatibus et magnis dis. Consequat semper viverra nam
/// libero. Accumsan in nisl nisi scelerisque eu. Pellentesque id nibh tortor id aliquet. Sed
/// velit dignissim sodales ut. Facilisis sed odio morbi quis commodo odio aenean sed. Et
/// ultrices neque ornare aenean euismod elementum. Condimentum lacinia quis vel eros donec ac
/// odio tempor.
///
/// Lacinia at quis risus sed vulputate odio ut enim. Etiam erat velit scelerisque in dictum.
/// Nibh tellus molestie nunc non blandit massa enim nec. Nascetur ridiculus mus mauris vitae.
pub bar: u32,
/// Mi proin sed libero enim sed faucibus turpis. Amet consectetur adipiscing elit duis
/// tristique sollicitudin nibh sit amet. Congue quisque egestas diam in arcu cursus euismod
/// quis viverra. Cum sociis natoque penatibus et magnis dis parturient montes. Enim sit amet
/// venenatis urna cursus eget nunc scelerisque viverra. Cras semper auctor neque vitae tempus
/// quam pellentesque. Tortor posuere ac ut consequat semper viverra nam libero justo. Vitae
/// auctor eu augue ut lectus arcu bibendum at. Faucibus vitae aliquet nec ullamcorper sit amet
/// risus nullam. Maecenas accumsan lacus vel facilisis volutpat. Arcu non odio euismod
/// lacinia.
///
/// [`FooBar::beep()`]: crate::foobar::FooBar::beep
/// [`FooBar::boop()`]: crate::foobar::FooBar::boop
/// [`foobar::BazBaq::BEEP_BOOP`]: crate::foobar::BazBaq::BEEP_BOOP
pub baz: u32,
/// Eu consequat ac felis donec et odio pellentesque diam. Ut eu sem integer vitae justo eget.
/// Consequat ac felis donec et odio pellentesque diam volutpat.
pub baq: u32,
/// Amet consectetur adipiscing elit pellentesque habitant. Ut morbi tincidunt augue interdum
/// velit euismod in pellentesque. Imperdiet sed euismod nisi porta lorem. Nec tincidunt
/// praesent semper feugiat. Facilisis leo vel fringilla est. Egestas diam in arcu cursus
/// euismod quis viverra. Sagittis eu volutpat odio facilisis mauris sit amet. Posuere morbi
/// leo urna molestie at.
///
/// Pretium aenean pharetra magna ac. Nisl condimentum id venenatis a condimentum vitae. Semper
/// quis lectus nulla at volutpat diam ut venenatis tellus. Egestas tellus rutrum tellus
/// pellentesque eu tincidunt tortor aliquam.
pub foobar: u32
}

Some files were not shown because too many files have changed in this diff Show More