]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/duplicate_mod.rs
7ff7068f0b05e56aec583ec5dd066ab280109193
[rustc.git] / src / tools / clippy / clippy_lints / src / duplicate_mod.rs
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, Level, 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.63.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 lint_levels: Vec<Level>,
53 }
54
55 #[derive(Default)]
56 pub struct DuplicateMod {
57 /// map from the canonicalized path to `Modules`, `BTreeMap` to make the
58 /// order deterministic for tests
59 modules: BTreeMap<PathBuf, Modules>,
60 }
61
62 impl_lint_pass!(DuplicateMod => [DUPLICATE_MOD]);
63
64 impl EarlyLintPass for DuplicateMod {
65 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
66 if let ItemKind::Mod(_, ModKind::Loaded(_, Inline::No, mod_spans)) = &item.kind
67 && let FileName::Real(real) = cx.sess().source_map().span_to_filename(mod_spans.inner_span)
68 && let Some(local_path) = real.into_local_path()
69 && let Ok(absolute_path) = local_path.canonicalize()
70 {
71 let modules = self.modules.entry(absolute_path).or_insert(Modules {
72 local_path,
73 spans: Vec::new(),
74 lint_levels: Vec::new(),
75 });
76 modules.spans.push(item.span_with_attributes());
77 modules.lint_levels.push(cx.get_lint_level(DUPLICATE_MOD));
78 }
79 }
80
81 fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
82 for Modules {
83 local_path,
84 spans,
85 lint_levels,
86 } in self.modules.values()
87 {
88 if spans.len() < 2 {
89 continue;
90 }
91
92 // At this point the lint would be emitted
93 assert_eq!(spans.len(), lint_levels.len());
94 let spans: Vec<_> = spans
95 .iter()
96 .zip(lint_levels)
97 .filter_map(|(span, lvl)| {
98 if let Some(id) = lvl.get_expectation_id() {
99 cx.fulfill_expectation(id);
100 }
101
102 (!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span)
103 })
104 .collect();
105
106 if spans.len() < 2 {
107 continue;
108 }
109
110 let mut multi_span = MultiSpan::from_spans(spans.clone());
111 let (&first, duplicates) = spans.split_first().unwrap();
112
113 multi_span.push_span_label(first, "first loaded here");
114 for &duplicate in duplicates {
115 multi_span.push_span_label(duplicate, "loaded again here");
116 }
117
118 span_lint_and_help(
119 cx,
120 DUPLICATE_MOD,
121 multi_span,
122 &format!("file is loaded as a module multiple times: `{}`", local_path.display()),
123 None,
124 "replace all but one `mod` item with `use` items",
125 );
126 }
127 }
128 }