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