diff --git a/Configurations.md b/Configurations.md index 2d01fb3bb3b..70abb009e9e 100644 --- a/Configurations.md +++ b/Configurations.md @@ -1306,6 +1306,26 @@ If you want to ignore every file under the directory where you put your rustfmt. ignore = ["/"] ``` +## `report_missing_submod` + +Controls whether to report missing submodules and whether rustfmt will format. + +- **Default value**: `Error` +- **Possible values**: `Error`, `Warn`, `Ignore` +- **Stable**: No + +#### `Error` (default): + +Missing modules will lead to module not found errors and rustfmt won't format anything. + +#### `Warn` + +Missing modules will lead to module not found warnings and rustfmt will format. + +#### `Ignore` + +Ignores missing submodule error and rustfmt will format. + ## `imports_indent` Indent style of imports diff --git a/src/config/mod.rs b/src/config/mod.rs index 9484b2e5829..772b09dd8c4 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -179,6 +179,8 @@ create_config! { or they are left with trailing whitespaces"; ignore: IgnoreList, IgnoreList::default(), false, "Skip formatting the specified files and directories"; + report_missing_submod: ReportMissingSubmod, ReportMissingSubmod::Error, false, + "Report missing submodule"; // Not user-facing verbose: Verbosity, Verbosity::Normal, false, "How much to information to emit to the user"; @@ -697,6 +699,7 @@ show_parse_errors = true error_on_line_overflow = false error_on_unformatted = false ignore = [] +report_missing_submod = "Error" emit_mode = "Files" make_backup = false "#, diff --git a/src/config/options.rs b/src/config/options.rs index 3c5c713a33a..e9edc607f0b 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -494,3 +494,14 @@ pub enum StyleEdition { /// [Edition 2024](). Edition2024, } + +/// Controls whether to report missing submodules and whether rustfmt will format. +#[config_type] +pub enum ReportMissingSubmod { + /// Missing modules will lead to module not found errors and rustfmt won't format anything. + Error, + /// Missing modules will lead to module not found warnings and rustfmt will format. + Warn, + /// Ignores missing submodule error and rustfmt will format. + Ignore, +} diff --git a/src/formatting.rs b/src/formatting.rs index 60361505a3f..f24233c91d7 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -135,6 +135,7 @@ fn format_project( &context.parse_session, directory_ownership.unwrap_or(DirectoryOwnership::UnownedViaBlock), !input_is_stdin && !config.skip_children(), + config.report_missing_submod(), ) .visit_crate(&krate)? .into_iter() diff --git a/src/modules.rs b/src/modules.rs index af9a154a6ae..38f993fbcfa 100644 --- a/src/modules.rs +++ b/src/modules.rs @@ -10,7 +10,7 @@ use thin_vec::ThinVec; use thiserror::Error; use crate::attr::MetaVisitor; -use crate::config::FileName; +use crate::config::{FileName, ReportMissingSubmod}; use crate::items::is_mod_decl; use crate::parse::parser::{ Directory, DirectoryOwnership, ModError, ModulePathSuccess, Parser, ParserError, @@ -62,6 +62,7 @@ pub(crate) struct ModResolver<'ast, 'sess> { directory: Directory, file_map: FileModMap<'ast>, recursive: bool, + report_missing_submod: ReportMissingSubmod, } /// Represents errors while trying to resolve modules. @@ -105,6 +106,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { parse_sess: &'sess ParseSess, directory_ownership: DirectoryOwnership, recursive: bool, + report_missing_submod: ReportMissingSubmod, ) -> Self { ModResolver { directory: Directory { @@ -114,6 +116,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { file_map: BTreeMap::new(), parse_sess, recursive, + report_missing_submod, } } @@ -249,6 +252,20 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> { // mod foo; // Look for an extern file. self.find_external_module(item.ident, &item.attrs, sub_mod) + .or_else(|err| match err.kind { + ModuleResolutionErrorKind::NotFound { file: _ } + if self.report_missing_submod == ReportMissingSubmod::Ignore => + { + Ok(None) + } + ModuleResolutionErrorKind::NotFound { file: _ } + if self.report_missing_submod == ReportMissingSubmod::Warn => + { + eprintln!("Warning: {}", err); + Ok(None) + } + _ => Err(err), + }) } else { // An internal module (`mod foo { /* ... */ }`); Ok(Some(SubModKind::Internal(item))) diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs index 87b55ca1d1d..74eae3e9c08 100644 --- a/tests/rustfmt/main.rs +++ b/tests/rustfmt/main.rs @@ -1,8 +1,8 @@ //! Integration tests for rustfmt. use std::env; -use std::fs::remove_file; -use std::path::Path; +use std::fs::{read_to_string, remove_file}; +use std::path::{Path, PathBuf}; use std::process::Command; use rustfmt_config_proc_macro::rustfmt_only_ci_test; @@ -200,3 +200,47 @@ fn rustfmt_emits_error_when_control_brace_style_is_always_next_line() { let (_stdout, stderr) = rustfmt(&args); assert!(!stderr.contains("error[internal]: left behind trailing whitespace")) } + +#[test] +fn report_missing_sub_mod_error() { + // Ensure that missing submodules cause module not found errors when trying to + // resolve submodules with `skip_children=false` and `report_missing_submod=Error` + let args = [ + "--config=skip_children=false,report_missing_submod=Error", + "tests/source/issue-5609.rs", + ]; + let (_stdout, stderr) = rustfmt(&args); + // Module resolution fails because we're unable to find `missing_submod.rs` + assert!(stderr.contains("missing_submod.rs does not exist")) +} + +#[test] +fn report_missing_sub_mod_warn() { + // Ensure that missing submodules cause module not found warnings when trying to + // resolve submodules with `skip_children=false` and `report_missing_submod=Warn` + let args = [ + "--emit=stdout", + "--config=skip_children=false,report_missing_submod=Warn", + "tests/source/issue-5609.rs", + ]; + let (stdout, stderr) = rustfmt(&args); + // Module resolution succeed but we emit warnings because we're unable to find `missing_submod.rs` + assert!(stderr.contains("missing_submod.rs does not exist")); + + let target = read_to_string(PathBuf::from("tests/target/issue-5609.rs")).unwrap(); + assert!(stdout.ends_with(&target)); +} + +#[test] +fn ignore_missing_sub_mod_true() { + // Ensure that missing submodules don't cause module not found errors when trying to + // resolve submodules with `skip_children=false` and `report_missing_submod=Ignore`. + let args = [ + "--emit=stdout", + "--config=skip_children=false,report_missing_submod=Ignore", + "tests/source/issue-5609.rs", + ]; + let (stdout, _stderr) = rustfmt(&args); + let target = read_to_string(PathBuf::from("tests/target/issue-5609.rs")).unwrap(); + assert!(stdout.ends_with(&target)); +} diff --git a/tests/source/issue-5609-2.rs b/tests/source/issue-5609-2.rs new file mode 100644 index 00000000000..413c0829f4d --- /dev/null +++ b/tests/source/issue-5609-2.rs @@ -0,0 +1,8 @@ +// rustfmt-report_missing_submod: Warn +// rustfmt-skip_children: false + +mod missing_submod; + +fn test() { + +} diff --git a/tests/source/issue-5609.rs b/tests/source/issue-5609.rs new file mode 100644 index 00000000000..d7112a4d262 --- /dev/null +++ b/tests/source/issue-5609.rs @@ -0,0 +1,8 @@ +// rustfmt-report_missing_submod: Ignore +// rustfmt-skip_children: false + +mod missing_submod; + +fn test() { + +} diff --git a/tests/target/issue-5609-2.rs b/tests/target/issue-5609-2.rs new file mode 100644 index 00000000000..f7b454216bf --- /dev/null +++ b/tests/target/issue-5609-2.rs @@ -0,0 +1,6 @@ +// rustfmt-report_missing_submod: Warn +// rustfmt-skip_children: false + +mod missing_submod; + +fn test() {} diff --git a/tests/target/issue-5609.rs b/tests/target/issue-5609.rs new file mode 100644 index 00000000000..184cc0aed60 --- /dev/null +++ b/tests/target/issue-5609.rs @@ -0,0 +1,6 @@ +// rustfmt-report_missing_submod: Ignore +// rustfmt-skip_children: false + +mod missing_submod; + +fn test() {}