use clippy_config::types::Rename; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; use rustc_data_structures::fx::FxHashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::def_id::DefId; use rustc_hir::{Item, ItemKind, UseKind}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::Symbol; declare_clippy_lint! { /// ### What it does /// Checks for imports that do not rename the item as specified /// in the `enforced-import-renames` config option. /// /// Note: Even though this lint is warn-by-default, it will only trigger if /// import renames are defined in the `clippy.toml` file. /// /// ### Why is this bad? /// Consistency is important; if a project has defined import renames, then they should be /// followed. More practically, some item names are too vague outside of their defining scope, /// in which case this can enforce a more meaningful naming. /// /// ### Example /// An example clippy.toml configuration: /// ```toml /// # clippy.toml /// enforced-import-renames = [ /// { path = "serde_json::Value", rename = "JsonValue" }, /// ] /// ``` /// /// ```rust,ignore /// use serde_json::Value; /// ``` /// Use instead: /// ```rust,ignore /// use serde_json::Value as JsonValue; /// ``` #[clippy::version = "1.55.0"] pub MISSING_ENFORCED_IMPORT_RENAMES, style, "enforce import renames" } pub struct ImportRename { conf_renames: Vec, renames: FxHashMap, } impl ImportRename { pub fn new(conf_renames: Vec) -> Self { Self { conf_renames, renames: FxHashMap::default(), } } } impl_lint_pass!(ImportRename => [MISSING_ENFORCED_IMPORT_RENAMES]); impl LateLintPass<'_> for ImportRename { fn check_crate(&mut self, cx: &LateContext<'_>) { for Rename { path, rename } in &self.conf_renames { let segs = path.split("::").collect::>(); for id in clippy_utils::def_path_def_ids(cx, &segs) { self.renames.insert(id, Symbol::intern(rename)); } } } fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Use(path, UseKind::Single) = &item.kind { for &res in &path.res { if let Res::Def(_, id) = res && let Some(name) = self.renames.get(&id) // Remove semicolon since it is not present for nested imports && let span_without_semi = cx.sess().source_map().span_until_char(item.span, ';') && let Some(snip) = snippet_opt(cx, span_without_semi) && let Some(import) = match snip.split_once(" as ") { None => Some(snip.as_str()), Some((import, rename)) => { if rename.trim() == name.as_str() { None } else { Some(import.trim()) } }, } { span_lint_and_sugg( cx, MISSING_ENFORCED_IMPORT_RENAMES, span_without_semi, "this import should be renamed", "try", format!("{import} as {name}",), Applicability::MachineApplicable, ); } } } } }