Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions Contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ Without explicit settings, the tests will be run using rustfmt's default
configuration. It is possible to run a test using non-default settings in several
ways. Firstly, you can include configuration parameters in comments at the top
of the file. For example: to use 3 spaces per tab, start your test with
`// rustfmt-tab_spaces: 3`. Just remember that the comment is part of the input,
`//@ rustfmt-tab_spaces: 3`. Just remember that the comment is part of the input,
so include in both the source and target files! It is also possible to
explicitly specify the name of the expected output file in the target directory.
Use `// rustfmt-target: filename.rs` for this. You can also specify a custom
Use `//@ rustfmt-target: filename.rs` for this. You can also specify a custom
configuration by using the `rustfmt-config` directive. Rustfmt will then use
that toml file located in `./tests/config/` for its configuration. Including
`// rustfmt-config: small_tabs.toml` will run your test with the configuration
`//@ rustfmt-config: small_tabs.toml` will run your test with the configuration
file found at `./tests/config/small_tabs.toml`. The final option is used when the
test source file contains no configuration parameter comments. In this case, the
test harness looks for a configuration file with the same filename as the test
Expand Down
12 changes: 12 additions & 0 deletions src/config/config_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,18 @@ macro_rules! create_config {
CliConfigWasSet(self)
}

#[allow(unreachable_pub)]
pub fn was_option_set(&self, key: &str) -> bool {
match key {
$(
stringify!($i) => {
ConfigWasSet(self).$i()
}
)+
_ => false
}
}

fn fill_from_parsed_config(mut self, parsed: PartialConfig, dir: &Path) -> Config {
$(
if let Some(option_value) = parsed.$i {
Expand Down
108 changes: 76 additions & 32 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ fn verify_config_used(path: &Path, config_name: &str) {
let entry = entry.expect("couldn't get directory entry");
let path = entry.path();
if path.extension().map_or(false, |f| f == "rs") {
// check if "// rustfmt-<config_name>:" appears in the file.
// check if "//@ rustfmt-<config_name>:" appears in the file.
let filebuf = BufReader::new(
fs::File::open(&path)
.unwrap_or_else(|_| panic!("couldn't read file {}", path.display())),
Expand All @@ -137,7 +137,7 @@ fn verify_config_used(path: &Path, config_name: &str) {
.lines()
.map(Result::unwrap)
.take_while(|l| l.starts_with("//"))
.any(|l| l.starts_with(&format!("// rustfmt-{}", config_name))),
.any(|l| l.starts_with(&format!("//@ rustfmt-{}", config_name))),
"config option file {} does not contain expected config name",
path.display()
);
Expand Down Expand Up @@ -258,7 +258,7 @@ fn warning_tests() {
let files = get_test_files(Path::new("tests/warning/source"), true);

for file in &files {
let mut config = read_config(file);
let mut config = read_config(file).expect("config is valid");

@matthewhughes934 matthewhughes934 Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this causes confusing error messages, e.g. using //@ rustfmt-foo: true, the error printed is:

config is valid: "tests/source/doc.rs set unrecognized config option \"foo\""

Reading that: is the error telling me the config is valid?. The docs recommend using should, and I think that would work:

Suggested change
let mut config = read_config(file).expect("config is valid");
let mut config = read_config(file).expect("config should be valid");

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on updating all the expect calls to say something like "invalid config" or "expected valid config"?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on updating all the expect calls to say something like "invalid config" or "expected valid config"?

👍 sounds good to me

config.set().error_on_line_overflow(true);
config.set().error_on_unformatted(true);

Expand Down Expand Up @@ -379,7 +379,7 @@ fn modified_test() {
// Helper function for comparing the results of rustfmt
// to a known output file generated by one of the write modes.
fn assert_output(source: &Path, expected_filename: &Path) {
let config = read_config(source);
let config = read_config(source).expect("config is valid");
let (_, source_file, _) = format_file(source, config.clone());

// Populate output by writing to a vec.
Expand Down Expand Up @@ -790,7 +790,8 @@ fn check_files(files: Vec<PathBuf>, opt_config: &Option<PathBuf>) -> (Vec<Format
let mut reports = vec![];

for file_name in files {
let sig_comments = read_significant_comments(&file_name);
let sig_comments =
read_significant_comments(&file_name).expect("valid significant comments");
if sig_comments.contains_key("unstable") && !is_nightly_channel!() {
debug!(
"Skipping '{}' because it requires unstable \
Expand Down Expand Up @@ -856,8 +857,8 @@ fn print_mismatches<T: Fn(u32) -> String>(
}
}

fn read_config(filename: &Path) -> Config {
let sig_comments = read_significant_comments(filename);
fn read_config(filename: &Path) -> Result<Config, String> {
let sig_comments = read_significant_comments(filename)?;
let (edition, style_edition, version) = get_editions_from_comments(&sig_comments);
// Look for a config file. If there is a 'config' property in the significant comments, use
// that. Otherwise, if there are no significant comments at all, look for a config file with
Expand All @@ -880,11 +881,40 @@ fn read_config(filename: &Path) -> Config {

for (key, val) in &sig_comments {
if key != "target" && key != "config" && key != "unstable" && key != "stable" {
if !Config::is_valid_name(key) {
// Check that all config names are valid
return Err(format!(
"{} set unrecognized config option {:?}",
&filename.display(),
key
));
}

if !Config::is_valid_key_val(key, val) {
// Check that all config values are valid
return Err(format!(
"{} set unrecognized value {:?} for config option {:?}",
&filename.display(),
key,
val
));
}

config.override_value(key, val);

if !config.was_option_set(key) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what motivated adding this check/how can we end up in this situation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an extra check to make sure that values are actually getting set as we'd expect. I don't see any harm in keeping this check, but let me know if you think otherwise.

// Check that all config values were properly set
return Err(format!(
"{} didn't correctly set {}: {}",
&filename.display(),
key,
val
));
}
}
}

config
Ok(config)
}

fn format_file<P: Into<PathBuf>>(filepath: P, config: Config) -> (bool, SourceFile, FormatReport) {
Expand Down Expand Up @@ -923,13 +953,13 @@ fn idempotent_check(
filename: &PathBuf,
opt_config: &Option<PathBuf>,
) -> Result<FormatReport, IdempotentCheckError> {
let sig_comments = read_significant_comments(filename);
let sig_comments = read_significant_comments(filename).expect("valid significant comments");
let config = if let Some(ref config_file_path) = opt_config {
let (edition, style_edition, version) = get_editions_from_comments(&sig_comments);
Config::from_toml_path(config_file_path, edition, style_edition, version)
.expect("`rustfmt.toml` not found")
} else {
read_config(filename)
read_config(filename).expect("config is valid")
};
let (parsing_errors, source_file, format_report) = format_file(filename, config);
if parsing_errors {
Expand Down Expand Up @@ -985,37 +1015,51 @@ fn get_config(
.expect("invalid TOML")
}

// Reads significant comments of the form: `// rustfmt-key: value` into a hash map.
fn read_significant_comments(file_name: &Path) -> HashMap<String, String> {
// Reads significant comments of the form: `//@ rustfmt-key: value` into a hash map.
fn read_significant_comments(file_name: &Path) -> Result<HashMap<String, String>, String> {
let file = fs::File::open(file_name)
.unwrap_or_else(|_| panic!("couldn't read file {}", file_name.display()));
let reader = BufReader::new(file);
let pattern = r"^\s*//\s*rustfmt-([^:]+):\s*(\S+)";
let regex = regex::Regex::new(pattern).expect("failed creating pattern 1");

// Matches lines containing significant comments or whitespace.
let line_regex = regex::Regex::new(r"(^\s*$)|(^\s*//\s*rustfmt-[^:]+:\s*\S+)")
.expect("failed creating pattern 2");
// Matches `// rustfmt-{name}: {value}`
let old_pattern = r"^\s*//\s+rustfmt-(?P<name>[A-Za-z_]+)\s?:\s*(?P<value>\S+)$";
let old_regex = regex::Regex::new(old_pattern).expect("failed creating old pattern");

// Matches `//@ rustfmt-{name}: {value}`
let pattern = r"^\s*//@\s+rustfmt-(?P<name>[A-Za-z_]+)\s?:\s*(?P<value>\S+)$";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything us from being stricter here, there are currently no comments in files that need the extra optional spaces

Suggested change
let pattern = r"^\s*//@\s+rustfmt-(?P<name>[A-Za-z_]+)\s?:\s*(?P<value>\S+)$";
let pattern = r"^//@\s+rustfmt-(?P<name>[A-Za-z_]+):\s*(?P<value>\S+)$";

verified via

# no comments use a space between line start and `//`
git grep --perl-regexp '^\s+//@\s+rustfmt-(?P<name>[A-Za-z_]+)\s?:\s*(?P<value>\S+)$' -- tests/
#                          ^ changed '*' to '+'
# similarly, no comment uses a space between 'rustfmt-<name>' and then ':'
git grep --perl-regexp '^\s*//@\s+rustfmt-(?P<name>[A-Za-z_]+)\s+:\s*(?P<value>\S+)$' -- tests/
#                                                               ^ changed '?' to '+'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably remove the second \s?, but I see some value in allowing arbitrary whitespace before the directive. Maybe we want to set the test directive in a nested comment instead of at the top of the file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably remove the second \s?, but I see some value in allowing arbitrary whitespace before the directive. Maybe we want to set the test directive in a nested comment instead of at the top of the file.

My preference is to limit things to what we know/expect we will need, then change it in the future if needed. But I'm not opposed to leaving it as-is.

let regex = regex::Regex::new(pattern).expect("failed creating pattern 1");

reader
.lines()
.map(|line| line.expect("failed getting line"))
.filter(|line| line_regex.is_match(line))
.filter(|l| l.trim_start().starts_with("//"))
.filter_map(|line| {
regex.captures_iter(&line).next().map(|capture| {
(
capture
.get(1)
.expect("couldn't unwrap capture")
.as_str()
.to_owned(),
capture
.get(2)
.expect("couldn't unwrap capture")
.as_str()
.to_owned(),
)
})
if let Some(c) = old_regex.captures(&line) {
// Don't let old syntax silently slip through.
// Guide test authors on how to fix issues with the old syntax.
return Some(Err(format!(
"{} uses old directive syntax. Should be //@ rustfmt-{}: {}",
&file_name.display(),
&c["name"],
&c["value"],
)));
}

if !line.trim_start().starts_with("//@") {
// This is just a normal comment line
return None;
}

let value = match regex.captures(&line) {
Some(c) => Ok((c["name"].to_owned(), c["value"].to_owned())),
None => Err(format!(
"{} has a malformed config directive. Should be //@ rustfmt-{}: {}",
&file_name.display(),
"{name}",
"{value}",
)),
};
Some(value)
})
.collect()
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/mod_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{FileName, Input, Session};

fn verify_mod_resolution(input_file_name: &str, exp_misformatted_files: &[&str]) {
let input_file = PathBuf::from(input_file_name);
let config = read_config(&input_file);
let config = read_config(&input_file).expect("config is valid");
let mut session = Session::<io::Stdout>::new(config, None);
let report = session
.format(Input::File(input_file_name.into()))
Expand Down
2 changes: 1 addition & 1 deletion src/test/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{ErrorKind, Input, Session};

/// Load the config, but hide expected parse errors
fn read_config_hide_parse_errors(filename: &std::path::Path) -> crate::Config {
let mut config = read_config(&filename);
let mut config = read_config(&filename).expect("config is valid");
config.set().show_parse_errors(false);
config
}
Expand Down
2 changes: 1 addition & 1 deletion tests/coverage/source/comments.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-emit_mode: coverage
//@ rustfmt-emit_mode: coverage
/// Here's a doc comment!
fn main() {
// foo is bar
Expand Down
2 changes: 1 addition & 1 deletion tests/coverage/target/comments.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
XX XXXXXXXXXXXXXXXXXX XXXXXXXX
XXX XXXXXXXXXXXXXXXXXX XXXXXXXX
/// Here's a doc comment!
fn main() {
XX XXX XX XXX
Expand Down
2 changes: 1 addition & 1 deletion tests/parser/try_macro_cant_be_chain_child.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-edition: 2015
//@ rustfmt-edition: 2015
// set edition for clarity, since 'try' became a reserved keyword in editions >=2018

fn main() {
Expand Down
2 changes: 1 addition & 1 deletion tests/source/5131_crate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-imports_granularity: Crate
//@ rustfmt-imports_granularity: Crate

use foo::a;
use foo::a;
Expand Down
2 changes: 1 addition & 1 deletion tests/source/5131_module.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-imports_granularity: Module
//@ rustfmt-imports_granularity: Module

#![allow(dead_code)]

Expand Down
2 changes: 1 addition & 1 deletion tests/source/5131_one.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-imports_granularity: One
//@ rustfmt-imports_granularity: One

pub use foo::x;
pub use foo::x as x2;
Expand Down
2 changes: 1 addition & 1 deletion tests/source/alignment_2633/block_style.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-struct_field_align_threshold: 50
//@ rustfmt-struct_field_align_threshold: 50

fn func() {
Ok(ServerInformation { name: unwrap_message_string(items.get(0)),
Expand Down
4 changes: 2 additions & 2 deletions tests/source/alignment_2633/visual_style.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// rustfmt-struct_field_align_threshold: 50
// rustfmt-indent_style: Visual
//@ rustfmt-struct_field_align_threshold: 50
//@ rustfmt-indent_style: Visual

fn func() {
Ok(ServerInformation { name: unwrap_message_string(items.get(0)),
Expand Down
2 changes: 1 addition & 1 deletion tests/source/arrow_in_comments/arrow_in_single_comment.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-style_edition: 2024
//@ rustfmt-style_edition: 2024
fn main() {
match a {
_ =>
Expand Down
2 changes: 1 addition & 1 deletion tests/source/arrow_in_comments/multiple_arrows.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-style_edition: 2024
//@ rustfmt-style_edition: 2024
fn main() {
match a {
_ => // comment with =>
Expand Down
2 changes: 1 addition & 1 deletion tests/source/async_block.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-edition: 2018
//@ rustfmt-edition: 2018

fn main() {
let x = async {
Expand Down
2 changes: 1 addition & 1 deletion tests/source/async_fn.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-edition: 2018
//@ rustfmt-edition: 2018

async fn bar() -> Result<(), ()> {
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion tests/source/attrib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-wrap_comments: true
//@ rustfmt-wrap_comments: true
// Test attributes and doc comments are preserved.
#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://doc.rust-lang.org/favicon.ico",
Expand Down
2 changes: 1 addition & 1 deletion tests/source/big-impl-visual.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-indent_style: Visual
//@ rustfmt-indent_style: Visual

// #1357
impl<
Expand Down
2 changes: 1 addition & 1 deletion tests/source/binop-separator-back/bitwise.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-binop_separator: Back
//@ rustfmt-binop_separator: Back

fn main() {
let value = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ ^ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;
Expand Down
2 changes: 1 addition & 1 deletion tests/source/binop-separator-back/comp.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-binop_separator: Back
//@ rustfmt-binop_separator: Back

fn main() {
if abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ < abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ {
Expand Down
2 changes: 1 addition & 1 deletion tests/source/binop-separator-back/logic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-binop_separator: Back
//@ rustfmt-binop_separator: Back

fn main() {
if abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ && abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ || abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ {
Expand Down
2 changes: 1 addition & 1 deletion tests/source/binop-separator-back/math.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-binop_separator: Back
//@ rustfmt-binop_separator: Back

fn main() {
let value = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;
Expand Down
2 changes: 1 addition & 1 deletion tests/source/binop-separator-back/patterns.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-binop_separator: Back
//@ rustfmt-binop_separator: Back

fn main() {
match val {
Expand Down
2 changes: 1 addition & 1 deletion tests/source/binop-separator-back/range.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-binop_separator: Back
//@ rustfmt-binop_separator: Back

fn main() {
let value = abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ..abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;
Expand Down
2 changes: 1 addition & 1 deletion tests/source/catch.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-edition: 2018
//@ rustfmt-edition: 2018
#![feature(try_blocks)]

fn main() {
Expand Down
6 changes: 3 additions & 3 deletions tests/source/cfg_select.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// rustfmt-unstable: true
// rustfmt-style_edition: 2024
// rustfmt-skip_children: true
//@ rustfmt-unstable: true
//@ rustfmt-style_edition: 2024
//@ rustfmt-skip_children: true

// empty cfg_select!
// Original `{}` delimiters
Expand Down
2 changes: 1 addition & 1 deletion tests/source/cfg_select_stable.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-stable: true
//@ rustfmt-stable: true

// While we gate the `cfg_select!` formatting behind the `is_nightly_channel!()` check
// this test helps ensure that we don't start formatting `cfg_select!` on the `stable`
Expand Down
2 changes: 1 addition & 1 deletion tests/source/chain_yield_with_comments.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-edition: 2024
//@ rustfmt-edition: 2024

fn f() {
parent /* comment */ .yield /* some comment */?// comment
Expand Down
2 changes: 1 addition & 1 deletion tests/source/chains-visual.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// rustfmt-indent_style: Visual
//@ rustfmt-indent_style: Visual
// Test chain formatting.

fn main() {
Expand Down
Loading
Loading