mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-30 18:53:39 +00:00
129 lines
3.8 KiB
Rust
129 lines
3.8 KiB
Rust
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::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",
|
|
);
|
|
}
|
|
}
|
|
}
|