]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/duplicate_mod.rs
Auto merge of #8901 - Jarcho:sharing_code, r=dswij
[rust.git] / 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, 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 }