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