summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints/src/duplicate_mod.rs
blob: 7ff7068f0b05e56aec583ec5dd066ab280109193 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_ast::ast::{Crate, Inline, Item, ItemKind, ModKind};
use rustc_errors::MultiSpan;
use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{FileName, Span};
use std::collections::BTreeMap;
use std::path::PathBuf;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for files that are included as modules multiple times.
    ///
    /// ### Why is this bad?
    /// Loading a file as a module more than once causes it to be compiled
    /// multiple times, taking longer and putting duplicate content into the
    /// module tree.
    ///
    /// ### Example
    /// ```rust,ignore
    /// // lib.rs
    /// mod a;
    /// mod b;
    /// ```
    /// ```rust,ignore
    /// // a.rs
    /// #[path = "./b.rs"]
    /// mod b;
    /// ```
    ///
    /// Use instead:
    ///
    /// ```rust,ignore
    /// // lib.rs
    /// mod a;
    /// mod b;
    /// ```
    /// ```rust,ignore
    /// // a.rs
    /// use crate::b;
    /// ```
    #[clippy::version = "1.63.0"]
    pub DUPLICATE_MOD,
    suspicious,
    "file loaded as module multiple times"
}

#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Modules {
    local_path: PathBuf,
    spans: Vec<Span>,
    lint_levels: Vec<Level>,
}

#[derive(Default)]
pub struct DuplicateMod {
    /// map from the canonicalized path to `Modules`, `BTreeMap` to make the
    /// order deterministic for tests
    modules: BTreeMap<PathBuf, Modules>,
}

impl_lint_pass!(DuplicateMod => [DUPLICATE_MOD]);

impl EarlyLintPass for DuplicateMod {
    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
        if let ItemKind::Mod(_, ModKind::Loaded(_, Inline::No, mod_spans)) = &item.kind
            && let FileName::Real(real) = cx.sess().source_map().span_to_filename(mod_spans.inner_span)
            && let Some(local_path) = real.into_local_path()
            && let Ok(absolute_path) = local_path.canonicalize()
        {
            let modules = self.modules.entry(absolute_path).or_insert(Modules {
                local_path,
                spans: Vec::new(),
                lint_levels: Vec::new(),
            });
            modules.spans.push(item.span_with_attributes());
            modules.lint_levels.push(cx.get_lint_level(DUPLICATE_MOD));
        }
    }

    fn check_crate_post(&mut self, cx: &EarlyContext<'_>, _: &Crate) {
        for Modules {
            local_path,
            spans,
            lint_levels,
        } in self.modules.values()
        {
            if spans.len() < 2 {
                continue;
            }

            // At this point the lint would be emitted
            assert_eq!(spans.len(), lint_levels.len());
            let spans: Vec<_> = spans
                .iter()
                .zip(lint_levels)
                .filter_map(|(span, lvl)| {
                    if let Some(id) = lvl.get_expectation_id() {
                        cx.fulfill_expectation(id);
                    }

                    (!matches!(lvl, Level::Allow | Level::Expect(_))).then_some(*span)
                })
                .collect();

            if spans.len() < 2 {
                continue;
            }

            let mut multi_span = MultiSpan::from_spans(spans.clone());
            let (&first, duplicates) = spans.split_first().unwrap();

            multi_span.push_span_label(first, "first loaded here");
            for &duplicate in duplicates {
                multi_span.push_span_label(duplicate, "loaded again here");
            }

            span_lint_and_help(
                cx,
                DUPLICATE_MOD,
                multi_span,
                &format!("file is loaded as a module multiple times: `{}`", local_path.display()),
                None,
                "replace all but one `mod` item with `use` items",
            );
        }
    }
}