]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_sugg; |
cdc7bbd5 | 2 | use clippy_utils::source::snippet; |
f20569fa XL |
3 | use hir::def::{DefKind, Res}; |
4 | use if_chain::if_chain; | |
5 | use rustc_ast::ast; | |
6 | use rustc_data_structures::fx::{FxHashMap, FxHashSet}; | |
7 | use rustc_errors::Applicability; | |
8 | use rustc_hir as hir; | |
9 | use rustc_lint::{LateContext, LateLintPass, LintContext}; | |
10 | use rustc_session::{declare_tool_lint, impl_lint_pass}; | |
cdc7bbd5 | 11 | use rustc_span::{edition::Edition, sym, Span}; |
f20569fa XL |
12 | |
13 | declare_clippy_lint! { | |
94222f64 XL |
14 | /// ### What it does |
15 | /// Checks for `#[macro_use] use...`. | |
f20569fa | 16 | /// |
94222f64 XL |
17 | /// ### Why is this bad? |
18 | /// Since the Rust 2018 edition you can import | |
f20569fa XL |
19 | /// macro's directly, this is considered idiomatic. |
20 | /// | |
94222f64 | 21 | /// ### Example |
f20569fa XL |
22 | /// ```rust,ignore |
23 | /// #[macro_use] | |
24 | /// use some_macro; | |
25 | /// ``` | |
a2a8927a | 26 | #[clippy::version = "1.44.0"] |
f20569fa XL |
27 | pub MACRO_USE_IMPORTS, |
28 | pedantic, | |
29 | "#[macro_use] is no longer needed" | |
30 | } | |
31 | ||
f20569fa XL |
32 | #[derive(Clone, Debug, PartialEq, Eq)] |
33 | struct PathAndSpan { | |
34 | path: String, | |
35 | span: Span, | |
36 | } | |
37 | ||
c295e0f8 | 38 | /// `MacroRefData` includes the name of the macro. |
f20569fa XL |
39 | #[derive(Debug, Clone)] |
40 | pub struct MacroRefData { | |
41 | name: String, | |
f20569fa XL |
42 | } |
43 | ||
44 | impl MacroRefData { | |
c295e0f8 XL |
45 | pub fn new(name: String) -> Self { |
46 | Self { name } | |
f20569fa XL |
47 | } |
48 | } | |
49 | ||
50 | #[derive(Default)] | |
923072b8 | 51 | #[expect(clippy::module_name_repetitions)] |
f20569fa XL |
52 | pub struct MacroUseImports { |
53 | /// the actual import path used and the span of the attribute above it. | |
54 | imports: Vec<(String, Span)>, | |
55 | /// the span of the macro reference, kept to ensure only one reference is used per macro call. | |
56 | collected: FxHashSet<Span>, | |
57 | mac_refs: Vec<MacroRefData>, | |
58 | } | |
59 | ||
60 | impl_lint_pass!(MacroUseImports => [MACRO_USE_IMPORTS]); | |
61 | ||
62 | impl MacroUseImports { | |
63 | fn push_unique_macro(&mut self, cx: &LateContext<'_>, span: Span) { | |
64 | let call_site = span.source_callsite(); | |
65 | let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_"); | |
c295e0f8 XL |
66 | if span.source_callee().is_some() && !self.collected.contains(&call_site) { |
67 | let name = if name.contains("::") { | |
68 | name.split("::").last().unwrap().to_string() | |
69 | } else { | |
70 | name.to_string() | |
71 | }; | |
f20569fa | 72 | |
c295e0f8 XL |
73 | self.mac_refs.push(MacroRefData::new(name)); |
74 | self.collected.insert(call_site); | |
f20569fa XL |
75 | } |
76 | } | |
77 | ||
78 | fn push_unique_macro_pat_ty(&mut self, cx: &LateContext<'_>, span: Span) { | |
79 | let call_site = span.source_callsite(); | |
80 | let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_"); | |
c295e0f8 XL |
81 | if span.source_callee().is_some() && !self.collected.contains(&call_site) { |
82 | self.mac_refs.push(MacroRefData::new(name.to_string())); | |
83 | self.collected.insert(call_site); | |
f20569fa XL |
84 | } |
85 | } | |
86 | } | |
87 | ||
88 | impl<'tcx> LateLintPass<'tcx> for MacroUseImports { | |
89 | fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { | |
90 | if_chain! { | |
91 | if cx.sess().opts.edition >= Edition::Edition2018; | |
92 | if let hir::ItemKind::Use(path, _kind) = &item.kind; | |
93 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
cdc7bbd5 | 94 | if let Some(mac_attr) = attrs.iter().find(|attr| attr.has_name(sym::macro_use)); |
f20569fa | 95 | if let Res::Def(DefKind::Mod, id) = path.res; |
cdc7bbd5 | 96 | if !id.is_local(); |
f20569fa | 97 | then { |
5099ac24 | 98 | for kid in cx.tcx.module_children(id).iter() { |
f20569fa XL |
99 | if let Res::Def(DefKind::Macro(_mac_type), mac_id) = kid.res { |
100 | let span = mac_attr.span; | |
101 | let def_path = cx.tcx.def_path_str(mac_id); | |
102 | self.imports.push((def_path, span)); | |
103 | } | |
104 | } | |
105 | } else { | |
5099ac24 | 106 | if item.span.from_expansion() { |
f20569fa XL |
107 | self.push_unique_macro_pat_ty(cx, item.span); |
108 | } | |
109 | } | |
110 | } | |
111 | } | |
112 | fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) { | |
5099ac24 | 113 | if attr.span.from_expansion() { |
f20569fa XL |
114 | self.push_unique_macro(cx, attr.span); |
115 | } | |
116 | } | |
117 | fn check_expr(&mut self, cx: &LateContext<'_>, expr: &hir::Expr<'_>) { | |
5099ac24 | 118 | if expr.span.from_expansion() { |
f20569fa XL |
119 | self.push_unique_macro(cx, expr.span); |
120 | } | |
121 | } | |
122 | fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) { | |
5099ac24 | 123 | if stmt.span.from_expansion() { |
f20569fa XL |
124 | self.push_unique_macro(cx, stmt.span); |
125 | } | |
126 | } | |
127 | fn check_pat(&mut self, cx: &LateContext<'_>, pat: &hir::Pat<'_>) { | |
5099ac24 | 128 | if pat.span.from_expansion() { |
f20569fa XL |
129 | self.push_unique_macro_pat_ty(cx, pat.span); |
130 | } | |
131 | } | |
132 | fn check_ty(&mut self, cx: &LateContext<'_>, ty: &hir::Ty<'_>) { | |
5099ac24 | 133 | if ty.span.from_expansion() { |
f20569fa XL |
134 | self.push_unique_macro_pat_ty(cx, ty.span); |
135 | } | |
136 | } | |
c295e0f8 | 137 | fn check_crate_post(&mut self, cx: &LateContext<'_>) { |
f20569fa XL |
138 | let mut used = FxHashMap::default(); |
139 | let mut check_dup = vec![]; | |
140 | for (import, span) in &self.imports { | |
141 | let found_idx = self.mac_refs.iter().position(|mac| import.ends_with(&mac.name)); | |
142 | ||
143 | if let Some(idx) = found_idx { | |
144 | self.mac_refs.remove(idx); | |
145 | let seg = import.split("::").collect::<Vec<_>>(); | |
146 | ||
147 | match seg.as_slice() { | |
148 | // an empty path is impossible | |
149 | // a path should always consist of 2 or more segments | |
150 | [] | [_] => return, | |
151 | [root, item] => { | |
152 | if !check_dup.contains(&(*item).to_string()) { | |
153 | used.entry(((*root).to_string(), span)) | |
154 | .or_insert_with(Vec::new) | |
155 | .push((*item).to_string()); | |
156 | check_dup.push((*item).to_string()); | |
157 | } | |
158 | }, | |
159 | [root, rest @ ..] => { | |
160 | if rest.iter().all(|item| !check_dup.contains(&(*item).to_string())) { | |
161 | let filtered = rest | |
162 | .iter() | |
163 | .filter_map(|item| { | |
164 | if check_dup.contains(&(*item).to_string()) { | |
165 | None | |
166 | } else { | |
167 | Some((*item).to_string()) | |
168 | } | |
169 | }) | |
170 | .collect::<Vec<_>>(); | |
171 | used.entry(((*root).to_string(), span)) | |
172 | .or_insert_with(Vec::new) | |
173 | .push(filtered.join("::")); | |
174 | check_dup.extend(filtered); | |
175 | } else { | |
176 | let rest = rest.to_vec(); | |
177 | used.entry(((*root).to_string(), span)) | |
178 | .or_insert_with(Vec::new) | |
179 | .push(rest.join("::")); | |
180 | check_dup.extend(rest.iter().map(ToString::to_string)); | |
181 | } | |
182 | }, | |
183 | } | |
184 | } | |
185 | } | |
186 | ||
187 | let mut suggestions = vec![]; | |
188 | for ((root, span), path) in used { | |
189 | if path.len() == 1 { | |
17df50a5 | 190 | suggestions.push((span, format!("{}::{}", root, path[0]))); |
f20569fa | 191 | } else { |
17df50a5 | 192 | suggestions.push((span, format!("{}::{{{}}}", root, path.join(", ")))); |
f20569fa XL |
193 | } |
194 | } | |
195 | ||
196 | // If mac_refs is not empty we have encountered an import we could not handle | |
197 | // such as `std::prelude::v1::foo` or some other macro that expands to an import. | |
198 | if self.mac_refs.is_empty() { | |
199 | for (span, import) in suggestions { | |
200 | let help = format!("use {};", import); | |
201 | span_lint_and_sugg( | |
202 | cx, | |
203 | MACRO_USE_IMPORTS, | |
204 | *span, | |
205 | "`macro_use` attributes are no longer needed in the Rust 2018 edition", | |
206 | "remove the attribute and import the macro directly, try", | |
207 | help, | |
208 | Applicability::MaybeIncorrect, | |
17df50a5 | 209 | ); |
f20569fa XL |
210 | } |
211 | } | |
212 | } | |
213 | } |