| use clippy_utils::diagnostics::span_lint_hir_and_then; | |
| use clippy_utils::source::snippet; | |
| use rustc_data_structures::fx::FxHashSet; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::def::{DefKind, Res}; | |
| use rustc_hir::{self as hir, AmbigArg, find_attr}; | |
| use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
| use rustc_session::impl_lint_pass; | |
| use rustc_span::Span; | |
| use rustc_span::edition::Edition; | |
| use std::collections::BTreeMap; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for `#[macro_use] use...`. | |
| /// | |
| /// ### Why is this bad? | |
| /// Since the Rust 2018 edition you can import | |
| /// macro's directly, this is considered idiomatic. | |
| /// | |
| /// ### Example | |
| /// ```rust,ignore | |
| /// #[macro_use] | |
| /// extern crate some_crate; | |
| /// | |
| /// fn main() { | |
| /// some_macro!(); | |
| /// } | |
| /// ``` | |
| /// | |
| /// Use instead: | |
| /// | |
| /// ```rust,ignore | |
| /// use some_crate::some_macro; | |
| /// | |
| /// fn main() { | |
| /// some_macro!(); | |
| /// } | |
| /// ``` | |
| #[clippy::version = "1.44.0"] | |
| pub MACRO_USE_IMPORTS, | |
| pedantic, | |
| "#[macro_use] is no longer needed" | |
| } | |
| impl_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]); | |
| /// `MacroRefData` includes the name of the macro. | |
| #[derive(Debug, Clone)] | |
| pub struct MacroRefData { | |
| name: String, | |
| } | |
| impl MacroRefData { | |
| pub fn new(name: String) -> Self { | |
| Self { name } | |
| } | |
| } | |
| #[derive(Default)] | |
| pub struct MacroUseImports { | |
| /// the actual import path used and the span of the attribute above it. The value is | |
| /// the location, where the lint should be emitted. | |
| imports: Vec<(String, Span, hir::HirId)>, | |
| /// the span of the macro reference, kept to ensure only one reference is used per macro call. | |
| collected: FxHashSet<Span>, | |
| mac_refs: Vec<MacroRefData>, | |
| } | |
| impl MacroUseImports { | |
| fn push_unique_macro(&mut self, cx: &LateContext<'_>, span: Span) { | |
| let call_site = span.source_callsite(); | |
| let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_"); | |
| if span.source_callee().is_some() && !self.collected.contains(&call_site) { | |
| let name = if name.contains("::") { | |
| name.split("::").last().unwrap().to_string() | |
| } else { | |
| name.to_string() | |
| }; | |
| self.mac_refs.push(MacroRefData::new(name)); | |
| self.collected.insert(call_site); | |
| } | |
| } | |
| fn push_unique_macro_pat_ty(&mut self, cx: &LateContext<'_>, span: Span) { | |
| let call_site = span.source_callsite(); | |
| let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_"); | |
| if span.source_callee().is_some() && !self.collected.contains(&call_site) { | |
| self.mac_refs.push(MacroRefData::new(name.to_string())); | |
| self.collected.insert(call_site); | |
| } | |
| } | |
| } | |
| impl LateLintPass<'_> for MacroUseImports { | |
| fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { | |
| if cx.sess().opts.edition >= Edition::Edition2018 | |
| && let hir::ItemKind::Use(path, _kind) = &item.kind | |
| && let hir_id = item.hir_id() | |
| && let attrs = cx.tcx.hir_attrs(hir_id) | |
| && let Some(mac_attr_span) = find_attr!(attrs, MacroUse {span, ..} => *span) | |
| && let Some(Res::Def(DefKind::Mod, id)) = path.res.type_ns | |
| && !id.is_local() | |
| { | |
| for kid in cx.tcx.module_children(id) { | |
| if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res { | |
| let def_path = cx.tcx.def_path_str(mac_id); | |
| self.imports.push((def_path, mac_attr_span, hir_id)); | |
| } | |
| } | |
| } else if item.span.from_expansion() { | |
| self.push_unique_macro_pat_ty(cx, item.span); | |
| } | |
| } | |
| fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { | |
| if expr.span.from_expansion() { | |
| self.push_unique_macro(cx, expr.span); | |
| } | |
| } | |
| fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) { | |
| if stmt.span.from_expansion() { | |
| self.push_unique_macro(cx, stmt.span); | |
| } | |
| } | |
| fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) { | |
| if pat.span.from_expansion() { | |
| self.push_unique_macro_pat_ty(cx, pat.span); | |
| } | |
| } | |
| fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_, AmbigArg>) { | |
| if ty.span.from_expansion() { | |
| self.push_unique_macro_pat_ty(cx, ty.span); | |
| } | |
| } | |
| fn check_crate_post(&mut self, cx: &LateContext<'_>) { | |
| let mut used = BTreeMap::new(); | |
| let mut check_dup = vec![]; | |
| for (import, span, hir_id) in &self.imports { | |
| let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name)); | |
| if let Some(idx) = found_idx { | |
| self.mac_refs.remove(idx); | |
| let seg = import.split("::").collect::<Vec<_>>(); | |
| match seg.as_slice() { | |
| // an empty path is impossible | |
| // a path should always consist of 2 or more segments | |
| [] | [_] => return, | |
| [root, item] => { | |
| if !check_dup.contains(&(*item).to_string()) { | |
| used.entry(( | |
| (*root).to_string(), | |
| span, | |
| hir_id.local_id, | |
| cx.tcx.def_path_hash(hir_id.owner.def_id.into()), | |
| )) | |
| .or_insert_with(|| (vec![], hir_id)) | |
| .0 | |
| .push((*item).to_string()); | |
| check_dup.push((*item).to_string()); | |
| } | |
| }, | |
| [root, rest @ ..] => { | |
| if rest.iter().all(|item| !check_dup.contains(&(*item).to_string())) { | |
| let filtered = rest | |
| .iter() | |
| .filter_map(|item| { | |
| if check_dup.contains(&(*item).to_string()) { | |
| None | |
| } else { | |
| Some((*item).to_string()) | |
| } | |
| }) | |
| .collect::<Vec<_>>(); | |
| used.entry(( | |
| (*root).to_string(), | |
| span, | |
| hir_id.local_id, | |
| cx.tcx.def_path_hash(hir_id.owner.def_id.into()), | |
| )) | |
| .or_insert_with(|| (vec![], hir_id)) | |
| .0 | |
| .push(filtered.join("::")); | |
| check_dup.extend(filtered); | |
| } else { | |
| let rest = rest.to_vec(); | |
| used.entry(( | |
| (*root).to_string(), | |
| span, | |
| hir_id.local_id, | |
| cx.tcx.def_path_hash(hir_id.owner.def_id.into()), | |
| )) | |
| .or_insert_with(|| (vec![], hir_id)) | |
| .0 | |
| .push(rest.join("::")); | |
| check_dup.extend(rest.iter().map(ToString::to_string)); | |
| } | |
| }, | |
| } | |
| } | |
| } | |
| // If mac_refs is not empty we have encountered an import we could not handle | |
| // such as `std::prelude::v1::foo` or some other macro that expands to an import. | |
| if self.mac_refs.is_empty() { | |
| for ((root, span, ..), (path, hir_id)) in used { | |
| let import = if let [single] = &path[..] { | |
| format!("{root}::{single}") | |
| } else { | |
| format!("{root}::{{{}}}", path.join(", ")) | |
| }; | |
| span_lint_hir_and_then( | |
| cx, | |
| MACRO_USE_IMPORTS, | |
| *hir_id, | |
| *span, | |
| "`macro_use` attributes are no longer needed in the Rust 2018 edition", | |
| |diag| { | |
| diag.span_suggestion( | |
| *span, | |
| "remove the attribute and import the macro directly, try", | |
| format!("use {import};"), | |
| Applicability::MaybeIncorrect, | |
| ); | |
| }, | |
| ); | |
| } | |
| } | |
| } | |
| } |