Skip to content

Commit 1f79a44

Browse files
committed
Add duplicate_mod lint
1 parent 2038084 commit 1f79a44

File tree

15 files changed

+178
-1
lines changed

15 files changed

+178
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3363,6 +3363,7 @@ Released 2018-09-13
33633363
[`drop_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_copy
33643364
[`drop_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_non_drop
33653365
[`drop_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#drop_ref
3366+
[`duplicate_mod`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_mod
33663367
[`duplicate_underscore_argument`]: https://rust-lang.github.io/rust-clippy/master/index.html#duplicate_underscore_argument
33673368
[`duration_subsec`]: https://rust-lang.github.io/rust-clippy/master/index.html#duration_subsec
33683369
[`else_if_without_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#else_if_without_else

clippy_lints/src/duplicate_mod.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use clippy_utils::diagnostics::span_lint_and_help;
2+
use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind};
3+
use rustc_errors::MultiSpan;
4+
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
5+
use rustc_session::{declare_tool_lint, impl_lint_pass};
6+
use rustc_span::{FileName, Span};
7+
use std::collections::BTreeMap;
8+
use std::path::PathBuf;
9+
10+
declare_clippy_lint! {
11+
/// ### What it does
12+
/// Checks for files that are included as modules multiple times.
13+
///
14+
/// ### Why is this bad?
15+
/// Loading a file as a module more than once causes it to be compiled
16+
/// multiple times, taking longer and putting duplicate content into the
17+
/// module tree.
18+
///
19+
/// ### Example
20+
/// ```rust,ignore
21+
/// // lib.rs
22+
/// mod a;
23+
/// mod b;
24+
/// ```
25+
/// ```rust,ignore
26+
/// // a.rs
27+
/// #[path = "./b.rs"]
28+
/// mod b;
29+
/// ```
30+
///
31+
/// Use instead:
32+
///
33+
/// ```rust,ignore
34+
/// // lib.rs
35+
/// mod a;
36+
/// mod b;
37+
/// ```
38+
/// ```rust,ignore
39+
/// // a.rs
40+
/// use crate::b;
41+
/// ```
42+
#[clippy::version = "1.62.0"]
43+
pub DUPLICATE_MOD,
44+
suspicious,
45+
"file loaded as module multiple times"
46+
}
47+
48+
#[derive(PartialOrd, Ord, PartialEq, Eq)]
49+
struct Modules {
50+
local_path: PathBuf,
51+
spans: Vec<Span>,
52+
}
53+
54+
#[derive(Default)]
55+
pub struct DuplicateMod {
56+
/// map from the canonicalized path to `Modules`, `BTreeMap` to make the
57+
/// order deterministic for tests
58+
modules: BTreeMap<PathBuf, Modules>,
59+
}
60+
61+
impl_lint_pass!(DuplicateMod => [DUPLICATE_MOD]);
62+
63+
impl EarlyLintPass for DuplicateMod {
64+
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
65+
if let ItemKind::Mod(_, ModKind::Loaded(_, Inline::No, mod_spans)) = &item.kind
66+
&& let FileName::Real(real) = cx.sess().source_map().span_to_filename(mod_spans.inner_span)
67+
&& let Some(local_path) = real.into_local_path()
68+
&& let Ok(absolute_path) = local_path.canonicalize()
69+
{
70+
let modules = self.modules.entry(absolute_path).or_insert(Modules {
71+
local_path,
72+
spans: Vec::new(),
73+
});
74+
modules.spans.push(item.span_with_attributes());
75+
}
76+
}
77+
78+
fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
79+
for Modules { local_path, spans } in self.modules.values() {
80+
if spans.len() < 2 {
81+
continue;
82+
}
83+
84+
let mut multi_span = MultiSpan::from_spans(spans.clone());
85+
let (&first, duplicates) = spans.split_first().unwrap();
86+
87+
multi_span.push_span_label(first, "first loaded here");
88+
for &duplicate in duplicates {
89+
multi_span.push_span_label(duplicate, "loaded again here");
90+
}
91+
92+
span_lint_and_help(
93+
cx,
94+
DUPLICATE_MOD,
95+
multi_span,
96+
&format!("file is loaded as a module multiple times: `{}`", local_path.display()),
97+
None,
98+
"replace all but one `mod` item with `use` items",
99+
);
100+
}
101+
}
102+
}

clippy_lints/src/lib.register_all.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
6060
LintId::of(drop_forget_ref::FORGET_NON_DROP),
6161
LintId::of(drop_forget_ref::FORGET_REF),
6262
LintId::of(drop_forget_ref::UNDROPPED_MANUALLY_DROPS),
63+
LintId::of(duplicate_mod::DUPLICATE_MOD),
6364
LintId::of(duration_subsec::DURATION_SUBSEC),
6465
LintId::of(entry::MAP_ENTRY),
6566
LintId::of(enum_clike::ENUM_CLIKE_UNPORTABLE_VARIANT),

clippy_lints/src/lib.register_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ store.register_lints(&[
133133
drop_forget_ref::FORGET_NON_DROP,
134134
drop_forget_ref::FORGET_REF,
135135
drop_forget_ref::UNDROPPED_MANUALLY_DROPS,
136+
duplicate_mod::DUPLICATE_MOD,
136137
duration_subsec::DURATION_SUBSEC,
137138
else_if_without_else::ELSE_IF_WITHOUT_ELSE,
138139
empty_drop::EMPTY_DROP,

clippy_lints/src/lib.register_suspicious.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ store.register_group(true, "clippy::suspicious", Some("clippy_suspicious"), vec!
1414
LintId::of(crate_in_macro_def::CRATE_IN_MACRO_DEF),
1515
LintId::of(drop_forget_ref::DROP_NON_DROP),
1616
LintId::of(drop_forget_ref::FORGET_NON_DROP),
17+
LintId::of(duplicate_mod::DUPLICATE_MOD),
1718
LintId::of(eval_order_dependence::EVAL_ORDER_DEPENDENCE),
1819
LintId::of(float_equality_without_abs::FLOAT_EQUALITY_WITHOUT_ABS),
1920
LintId::of(format_impl::PRINT_IN_FORMAT_IMPL),

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ mod doc;
211211
mod double_comparison;
212212
mod double_parens;
213213
mod drop_forget_ref;
214+
mod duplicate_mod;
214215
mod duration_subsec;
215216
mod else_if_without_else;
216217
mod empty_drop;
@@ -902,6 +903,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
902903
store.register_late_pass(move || Box::new(large_include_file::LargeIncludeFile::new(max_include_file_size)));
903904
store.register_late_pass(|| Box::new(strings::TrimSplitWhitespace));
904905
store.register_late_pass(|| Box::new(rc_clone_in_vec_init::RcCloneInVecInit));
906+
store.register_early_pass(|| Box::new(duplicate_mod::DuplicateMod::default()));
905907
// add lints here, do not remove this comment, it's used in `new_lint`
906908
}
907909

clippy_utils/src/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub fn span_lint<T: LintContext>(cx: &T, lint: &'static Lint, sp: impl Into<Mult
7777
pub fn span_lint_and_help<'a, T: LintContext>(
7878
cx: &'a T,
7979
lint: &'static Lint,
80-
span: Span,
80+
span: impl Into<MultiSpan>,
8181
msg: &str,
8282
help_span: Option<Span>,
8383
help: &str,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[package]
2+
name = "duplicate_mod"
3+
edition = "2021"
4+
publish = false
5+
version = "0.1.0"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
mod a;
2+
3+
mod b;
4+
#[path = "b.rs"]
5+
mod b2;
6+
7+
mod c;
8+
#[path = "c.rs"]
9+
mod c2;
10+
#[path = "c.rs"]
11+
mod c3;
12+
13+
mod from_other_module;
14+
mod other_module;
15+
16+
fn main() {}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
error: file is loaded as a module multiple times: `$DIR/b.rs`
2+
--> $DIR/main.rs:3:1
3+
|
4+
LL | mod b;
5+
| ^^^^^^ first loaded here
6+
LL | / #[path = "b.rs"]
7+
LL | | mod b2;
8+
| |_______^ loaded again here
9+
|
10+
= note: `-D clippy::duplicate-mod` implied by `-D warnings`
11+
= help: replace all but one `mod` item with `use` items
12+
13+
error: file is loaded as a module multiple times: `$DIR/c.rs`
14+
--> $DIR/main.rs:7:1
15+
|
16+
LL | mod c;
17+
| ^^^^^^ first loaded here
18+
LL | / #[path = "c.rs"]
19+
LL | | mod c2;
20+
| |_______^ loaded again here
21+
LL | / #[path = "c.rs"]
22+
LL | | mod c3;
23+
| |_______^ loaded again here
24+
|
25+
= help: replace all but one `mod` item with `use` items
26+
27+
error: file is loaded as a module multiple times: `$DIR/from_other_module.rs`
28+
--> $DIR/main.rs:13:1
29+
|
30+
LL | mod from_other_module;
31+
| ^^^^^^^^^^^^^^^^^^^^^^ first loaded here
32+
|
33+
::: $DIR/other_module/mod.rs:1:1
34+
|
35+
LL | / #[path = "../from_other_module.rs"]
36+
LL | | mod m;
37+
| |______^ loaded again here
38+
|
39+
= help: replace all but one `mod` item with `use` items
40+
41+
error: aborting due to 3 previous errors
42+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#[path = "../from_other_module.rs"]
2+
mod m;

0 commit comments

Comments
 (0)