]>
Commit | Line | Data |
---|---|---|
1 | //! checks for attributes | |
2 | ||
3 | use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; | |
4 | use clippy_utils::match_panic_def_id; | |
5 | use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments}; | |
6 | use if_chain::if_chain; | |
7 | use rustc_ast::{AttrKind, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem}; | |
8 | use rustc_errors::Applicability; | |
9 | use rustc_hir::{ | |
10 | Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind, | |
11 | }; | |
12 | use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext}; | |
13 | use rustc_middle::lint::in_external_macro; | |
14 | use rustc_middle::ty; | |
15 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
16 | use rustc_span::source_map::Span; | |
17 | use rustc_span::sym; | |
18 | use rustc_span::symbol::{Symbol, SymbolStr}; | |
19 | use semver::Version; | |
20 | ||
21 | static UNIX_SYSTEMS: &[&str] = &[ | |
22 | "android", | |
23 | "dragonfly", | |
24 | "emscripten", | |
25 | "freebsd", | |
26 | "fuchsia", | |
27 | "haiku", | |
28 | "illumos", | |
29 | "ios", | |
30 | "l4re", | |
31 | "linux", | |
32 | "macos", | |
33 | "netbsd", | |
34 | "openbsd", | |
35 | "redox", | |
36 | "solaris", | |
37 | "vxworks", | |
38 | ]; | |
39 | ||
40 | // NOTE: windows is excluded from the list because it's also a valid target family. | |
41 | static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"]; | |
42 | ||
43 | declare_clippy_lint! { | |
44 | /// **What it does:** Checks for items annotated with `#[inline(always)]`, | |
45 | /// unless the annotated function is empty or simply panics. | |
46 | /// | |
47 | /// **Why is this bad?** While there are valid uses of this annotation (and once | |
48 | /// you know when to use it, by all means `allow` this lint), it's a common | |
49 | /// newbie-mistake to pepper one's code with it. | |
50 | /// | |
51 | /// As a rule of thumb, before slapping `#[inline(always)]` on a function, | |
52 | /// measure if that additional function call really affects your runtime profile | |
53 | /// sufficiently to make up for the increase in compile time. | |
54 | /// | |
55 | /// **Known problems:** False positives, big time. This lint is meant to be | |
56 | /// deactivated by everyone doing serious performance work. This means having | |
57 | /// done the measurement. | |
58 | /// | |
59 | /// **Example:** | |
60 | /// ```ignore | |
61 | /// #[inline(always)] | |
62 | /// fn not_quite_hot_code(..) { ... } | |
63 | /// ``` | |
64 | pub INLINE_ALWAYS, | |
65 | pedantic, | |
66 | "use of `#[inline(always)]`" | |
67 | } | |
68 | ||
69 | declare_clippy_lint! { | |
70 | /// **What it does:** Checks for `extern crate` and `use` items annotated with | |
71 | /// lint attributes. | |
72 | /// | |
73 | /// This lint permits `#[allow(unused_imports)]`, `#[allow(deprecated)]`, | |
74 | /// `#[allow(unreachable_pub)]`, `#[allow(clippy::wildcard_imports)]` and | |
75 | /// `#[allow(clippy::enum_glob_use)]` on `use` items and `#[allow(unused_imports)]` on | |
76 | /// `extern crate` items with a `#[macro_use]` attribute. | |
77 | /// | |
78 | /// **Why is this bad?** Lint attributes have no effect on crate imports. Most | |
79 | /// likely a `!` was forgotten. | |
80 | /// | |
81 | /// **Known problems:** None. | |
82 | /// | |
83 | /// **Example:** | |
84 | /// ```ignore | |
85 | /// // Bad | |
86 | /// #[deny(dead_code)] | |
87 | /// extern crate foo; | |
88 | /// #[forbid(dead_code)] | |
89 | /// use foo::bar; | |
90 | /// | |
91 | /// // Ok | |
92 | /// #[allow(unused_imports)] | |
93 | /// use foo::baz; | |
94 | /// #[allow(unused_imports)] | |
95 | /// #[macro_use] | |
96 | /// extern crate baz; | |
97 | /// ``` | |
98 | pub USELESS_ATTRIBUTE, | |
99 | correctness, | |
100 | "use of lint attributes on `extern crate` items" | |
101 | } | |
102 | ||
103 | declare_clippy_lint! { | |
104 | /// **What it does:** Checks for `#[deprecated]` annotations with a `since` | |
105 | /// field that is not a valid semantic version. | |
106 | /// | |
107 | /// **Why is this bad?** For checking the version of the deprecation, it must be | |
108 | /// a valid semver. Failing that, the contained information is useless. | |
109 | /// | |
110 | /// **Known problems:** None. | |
111 | /// | |
112 | /// **Example:** | |
113 | /// ```rust | |
114 | /// #[deprecated(since = "forever")] | |
115 | /// fn something_else() { /* ... */ } | |
116 | /// ``` | |
117 | pub DEPRECATED_SEMVER, | |
118 | correctness, | |
119 | "use of `#[deprecated(since = \"x\")]` where x is not semver" | |
120 | } | |
121 | ||
122 | declare_clippy_lint! { | |
123 | /// **What it does:** Checks for empty lines after outer attributes | |
124 | /// | |
125 | /// **Why is this bad?** | |
126 | /// Most likely the attribute was meant to be an inner attribute using a '!'. | |
127 | /// If it was meant to be an outer attribute, then the following item | |
128 | /// should not be separated by empty lines. | |
129 | /// | |
130 | /// **Known problems:** Can cause false positives. | |
131 | /// | |
132 | /// From the clippy side it's difficult to detect empty lines between an attributes and the | |
133 | /// following item because empty lines and comments are not part of the AST. The parsing | |
134 | /// currently works for basic cases but is not perfect. | |
135 | /// | |
136 | /// **Example:** | |
137 | /// ```rust | |
138 | /// // Good (as inner attribute) | |
139 | /// #![allow(dead_code)] | |
140 | /// | |
141 | /// fn this_is_fine() { } | |
142 | /// | |
143 | /// // Bad | |
144 | /// #[allow(dead_code)] | |
145 | /// | |
146 | /// fn not_quite_good_code() { } | |
147 | /// | |
148 | /// // Good (as outer attribute) | |
149 | /// #[allow(dead_code)] | |
150 | /// fn this_is_fine_too() { } | |
151 | /// ``` | |
152 | pub EMPTY_LINE_AFTER_OUTER_ATTR, | |
153 | nursery, | |
154 | "empty line after outer attribute" | |
155 | } | |
156 | ||
157 | declare_clippy_lint! { | |
158 | /// **What it does:** Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category. | |
159 | /// | |
160 | /// **Why is this bad?** Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust. | |
161 | /// These lints should only be enabled on a lint-by-lint basis and with careful consideration. | |
162 | /// | |
163 | /// **Known problems:** None. | |
164 | /// | |
165 | /// **Example:** | |
166 | /// Bad: | |
167 | /// ```rust | |
168 | /// #![deny(clippy::restriction)] | |
169 | /// ``` | |
170 | /// | |
171 | /// Good: | |
172 | /// ```rust | |
173 | /// #![deny(clippy::as_conversions)] | |
174 | /// ``` | |
175 | pub BLANKET_CLIPPY_RESTRICTION_LINTS, | |
176 | style, | |
177 | "enabling the complete restriction group" | |
178 | } | |
179 | ||
180 | declare_clippy_lint! { | |
181 | /// **What it does:** Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it | |
182 | /// with `#[rustfmt::skip]`. | |
183 | /// | |
184 | /// **Why is this bad?** Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690)) | |
185 | /// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes. | |
186 | /// | |
187 | /// **Known problems:** This lint doesn't detect crate level inner attributes, because they get | |
188 | /// processed before the PreExpansionPass lints get executed. See | |
189 | /// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765) | |
190 | /// | |
191 | /// **Example:** | |
192 | /// | |
193 | /// Bad: | |
194 | /// ```rust | |
195 | /// #[cfg_attr(rustfmt, rustfmt_skip)] | |
196 | /// fn main() { } | |
197 | /// ``` | |
198 | /// | |
199 | /// Good: | |
200 | /// ```rust | |
201 | /// #[rustfmt::skip] | |
202 | /// fn main() { } | |
203 | /// ``` | |
204 | pub DEPRECATED_CFG_ATTR, | |
205 | complexity, | |
206 | "usage of `cfg_attr(rustfmt)` instead of tool attributes" | |
207 | } | |
208 | ||
209 | declare_clippy_lint! { | |
210 | /// **What it does:** Checks for cfg attributes having operating systems used in target family position. | |
211 | /// | |
212 | /// **Why is this bad?** The configuration option will not be recognised and the related item will not be included | |
213 | /// by the conditional compilation engine. | |
214 | /// | |
215 | /// **Known problems:** None. | |
216 | /// | |
217 | /// **Example:** | |
218 | /// | |
219 | /// Bad: | |
220 | /// ```rust | |
221 | /// #[cfg(linux)] | |
222 | /// fn conditional() { } | |
223 | /// ``` | |
224 | /// | |
225 | /// Good: | |
226 | /// ```rust | |
227 | /// #[cfg(target_os = "linux")] | |
228 | /// fn conditional() { } | |
229 | /// ``` | |
230 | /// | |
231 | /// Or: | |
232 | /// ```rust | |
233 | /// #[cfg(unix)] | |
234 | /// fn conditional() { } | |
235 | /// ``` | |
236 | /// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details. | |
237 | pub MISMATCHED_TARGET_OS, | |
238 | correctness, | |
239 | "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`" | |
240 | } | |
241 | ||
242 | declare_lint_pass!(Attributes => [ | |
243 | INLINE_ALWAYS, | |
244 | DEPRECATED_SEMVER, | |
245 | USELESS_ATTRIBUTE, | |
246 | BLANKET_CLIPPY_RESTRICTION_LINTS, | |
247 | ]); | |
248 | ||
249 | impl<'tcx> LateLintPass<'tcx> for Attributes { | |
250 | fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) { | |
251 | if let Some(items) = &attr.meta_item_list() { | |
252 | if let Some(ident) = attr.ident() { | |
253 | if is_lint_level(ident.name) { | |
254 | check_clippy_lint_names(cx, ident.name, items); | |
255 | } | |
256 | if items.is_empty() || !attr.has_name(sym::deprecated) { | |
257 | return; | |
258 | } | |
259 | for item in items { | |
260 | if_chain! { | |
261 | if let NestedMetaItem::MetaItem(mi) = &item; | |
262 | if let MetaItemKind::NameValue(lit) = &mi.kind; | |
263 | if mi.has_name(sym::since); | |
264 | then { | |
265 | check_semver(cx, item.span(), lit); | |
266 | } | |
267 | } | |
268 | } | |
269 | } | |
270 | } | |
271 | } | |
272 | ||
273 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { | |
274 | let attrs = cx.tcx.hir().attrs(item.hir_id()); | |
275 | if is_relevant_item(cx, item) { | |
276 | check_attrs(cx, item.span, item.ident.name, attrs) | |
277 | } | |
278 | match item.kind { | |
279 | ItemKind::ExternCrate(..) | ItemKind::Use(..) => { | |
280 | let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use)); | |
281 | ||
282 | for attr in attrs { | |
283 | if in_external_macro(cx.sess(), attr.span) { | |
284 | return; | |
285 | } | |
286 | if let Some(lint_list) = &attr.meta_item_list() { | |
287 | if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) { | |
288 | // permit `unused_imports`, `deprecated`, `unreachable_pub`, | |
289 | // `clippy::wildcard_imports`, and `clippy::enum_glob_use` for `use` items | |
290 | // and `unused_imports` for `extern crate` items with `macro_use` | |
291 | for lint in lint_list { | |
292 | match item.kind { | |
293 | ItemKind::Use(..) => { | |
294 | if is_word(lint, sym!(unused_imports)) | |
295 | || is_word(lint, sym::deprecated) | |
296 | || is_word(lint, sym!(unreachable_pub)) | |
297 | || is_word(lint, sym!(unused)) | |
298 | || extract_clippy_lint(lint).map_or(false, |s| s == "wildcard_imports") | |
299 | || extract_clippy_lint(lint).map_or(false, |s| s == "enum_glob_use") | |
300 | { | |
301 | return; | |
302 | } | |
303 | }, | |
304 | ItemKind::ExternCrate(..) => { | |
305 | if is_word(lint, sym!(unused_imports)) && skip_unused_imports { | |
306 | return; | |
307 | } | |
308 | if is_word(lint, sym!(unused_extern_crates)) { | |
309 | return; | |
310 | } | |
311 | }, | |
312 | _ => {}, | |
313 | } | |
314 | } | |
315 | let line_span = first_line_of_span(cx, attr.span); | |
316 | ||
317 | if let Some(mut sugg) = snippet_opt(cx, line_span) { | |
318 | if sugg.contains("#[") { | |
319 | span_lint_and_then( | |
320 | cx, | |
321 | USELESS_ATTRIBUTE, | |
322 | line_span, | |
323 | "useless lint attribute", | |
324 | |diag| { | |
325 | sugg = sugg.replacen("#[", "#![", 1); | |
326 | diag.span_suggestion( | |
327 | line_span, | |
328 | "if you just forgot a `!`, use", | |
329 | sugg, | |
330 | Applicability::MaybeIncorrect, | |
331 | ); | |
332 | }, | |
333 | ); | |
334 | } | |
335 | } | |
336 | } | |
337 | } | |
338 | } | |
339 | }, | |
340 | _ => {}, | |
341 | } | |
342 | } | |
343 | ||
344 | fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) { | |
345 | if is_relevant_impl(cx, item) { | |
346 | check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id())) | |
347 | } | |
348 | } | |
349 | ||
350 | fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { | |
351 | if is_relevant_trait(cx, item) { | |
352 | check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id())) | |
353 | } | |
354 | } | |
355 | } | |
356 | ||
357 | /// Returns the lint name if it is clippy lint. | |
358 | fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<SymbolStr> { | |
359 | if_chain! { | |
360 | if let Some(meta_item) = lint.meta_item(); | |
361 | if meta_item.path.segments.len() > 1; | |
362 | if let tool_name = meta_item.path.segments[0].ident; | |
363 | if tool_name.name == sym::clippy; | |
364 | then { | |
365 | let lint_name = meta_item.path.segments.last().unwrap().ident.name; | |
366 | return Some(lint_name.as_str()); | |
367 | } | |
368 | } | |
369 | None | |
370 | } | |
371 | ||
372 | fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) { | |
373 | for lint in items { | |
374 | if let Some(lint_name) = extract_clippy_lint(lint) { | |
375 | if lint_name == "restriction" && name != sym::allow { | |
376 | span_lint_and_help( | |
377 | cx, | |
378 | BLANKET_CLIPPY_RESTRICTION_LINTS, | |
379 | lint.span(), | |
380 | "restriction lints are not meant to be all enabled", | |
381 | None, | |
382 | "try enabling only the lints you really need", | |
383 | ); | |
384 | } | |
385 | } | |
386 | } | |
387 | } | |
388 | ||
389 | fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool { | |
390 | if let ItemKind::Fn(_, _, eid) = item.kind { | |
391 | is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value) | |
392 | } else { | |
393 | true | |
394 | } | |
395 | } | |
396 | ||
397 | fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool { | |
398 | match item.kind { | |
399 | ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value), | |
400 | _ => false, | |
401 | } | |
402 | } | |
403 | ||
404 | fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool { | |
405 | match item.kind { | |
406 | TraitItemKind::Fn(_, TraitFn::Required(_)) => true, | |
407 | TraitItemKind::Fn(_, TraitFn::Provided(eid)) => { | |
408 | is_relevant_expr(cx, cx.tcx.typeck_body(eid), &cx.tcx.hir().body(eid).value) | |
409 | }, | |
410 | _ => false, | |
411 | } | |
412 | } | |
413 | ||
414 | fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool { | |
415 | block.stmts.first().map_or( | |
416 | block | |
417 | .expr | |
418 | .as_ref() | |
419 | .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)), | |
420 | |stmt| match &stmt.kind { | |
421 | StmtKind::Local(_) => true, | |
422 | StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr), | |
423 | StmtKind::Item(_) => false, | |
424 | }, | |
425 | ) | |
426 | } | |
427 | ||
428 | fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool { | |
429 | match &expr.kind { | |
430 | ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block), | |
431 | ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e), | |
432 | ExprKind::Ret(None) | ExprKind::Break(_, None) => false, | |
433 | ExprKind::Call(path_expr, _) => { | |
434 | if let ExprKind::Path(qpath) = &path_expr.kind { | |
435 | typeck_results | |
436 | .qpath_res(qpath, path_expr.hir_id) | |
437 | .opt_def_id() | |
438 | .map_or(true, |fun_id| !match_panic_def_id(cx, fun_id)) | |
439 | } else { | |
440 | true | |
441 | } | |
442 | }, | |
443 | _ => true, | |
444 | } | |
445 | } | |
446 | ||
447 | fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) { | |
448 | if span.from_expansion() { | |
449 | return; | |
450 | } | |
451 | ||
452 | for attr in attrs { | |
453 | if let Some(values) = attr.meta_item_list() { | |
454 | if values.len() != 1 || !attr.has_name(sym::inline) { | |
455 | continue; | |
456 | } | |
457 | if is_word(&values[0], sym::always) { | |
458 | span_lint( | |
459 | cx, | |
460 | INLINE_ALWAYS, | |
461 | attr.span, | |
462 | &format!( | |
463 | "you have declared `#[inline(always)]` on `{}`. This is usually a bad idea", | |
464 | name | |
465 | ), | |
466 | ); | |
467 | } | |
468 | } | |
469 | } | |
470 | } | |
471 | ||
472 | fn check_semver(cx: &LateContext<'_>, span: Span, lit: &Lit) { | |
473 | if let LitKind::Str(is, _) = lit.kind { | |
474 | if Version::parse(&is.as_str()).is_ok() { | |
475 | return; | |
476 | } | |
477 | } | |
478 | span_lint( | |
479 | cx, | |
480 | DEPRECATED_SEMVER, | |
481 | span, | |
482 | "the since field must contain a semver-compliant version", | |
483 | ); | |
484 | } | |
485 | ||
486 | fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool { | |
487 | if let NestedMetaItem::MetaItem(mi) = &nmi { | |
488 | mi.is_word() && mi.has_name(expected) | |
489 | } else { | |
490 | false | |
491 | } | |
492 | } | |
493 | ||
494 | declare_lint_pass!(EarlyAttributes => [ | |
495 | DEPRECATED_CFG_ATTR, | |
496 | MISMATCHED_TARGET_OS, | |
497 | EMPTY_LINE_AFTER_OUTER_ATTR, | |
498 | ]); | |
499 | ||
500 | impl EarlyLintPass for EarlyAttributes { | |
501 | fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) { | |
502 | check_empty_line_after_outer_attr(cx, item); | |
503 | } | |
504 | ||
505 | fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) { | |
506 | check_deprecated_cfg_attr(cx, attr); | |
507 | check_mismatched_target_os(cx, attr); | |
508 | } | |
509 | } | |
510 | ||
511 | fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) { | |
512 | for attr in &item.attrs { | |
513 | let attr_item = if let AttrKind::Normal(ref attr, _) = attr.kind { | |
514 | attr | |
515 | } else { | |
516 | return; | |
517 | }; | |
518 | ||
519 | if attr.style == AttrStyle::Outer { | |
520 | if attr_item.args.inner_tokens().is_empty() || !is_present_in_source(cx, attr.span) { | |
521 | return; | |
522 | } | |
523 | ||
524 | let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt()); | |
525 | let end_of_attr_to_item = Span::new(attr.span.hi(), item.span.lo(), item.span.ctxt()); | |
526 | ||
527 | if let Some(snippet) = snippet_opt(cx, end_of_attr_to_item) { | |
528 | let lines = snippet.split('\n').collect::<Vec<_>>(); | |
529 | let lines = without_block_comments(lines); | |
530 | ||
531 | if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 { | |
532 | span_lint( | |
533 | cx, | |
534 | EMPTY_LINE_AFTER_OUTER_ATTR, | |
535 | begin_of_attr_to_item, | |
536 | "found an empty line after an outer attribute. \ | |
537 | Perhaps you forgot to add a `!` to make it an inner attribute?", | |
538 | ); | |
539 | } | |
540 | } | |
541 | } | |
542 | } | |
543 | } | |
544 | ||
545 | fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute) { | |
546 | if_chain! { | |
547 | // check cfg_attr | |
548 | if attr.has_name(sym::cfg_attr); | |
549 | if let Some(items) = attr.meta_item_list(); | |
550 | if items.len() == 2; | |
551 | // check for `rustfmt` | |
552 | if let Some(feature_item) = items[0].meta_item(); | |
553 | if feature_item.has_name(sym::rustfmt); | |
554 | // check for `rustfmt_skip` and `rustfmt::skip` | |
555 | if let Some(skip_item) = &items[1].meta_item(); | |
556 | if skip_item.has_name(sym!(rustfmt_skip)) || | |
557 | skip_item.path.segments.last().expect("empty path in attribute").ident.name == sym::skip; | |
558 | // Only lint outer attributes, because custom inner attributes are unstable | |
559 | // Tracking issue: https://github.com/rust-lang/rust/issues/54726 | |
560 | if let AttrStyle::Outer = attr.style; | |
561 | then { | |
562 | span_lint_and_sugg( | |
563 | cx, | |
564 | DEPRECATED_CFG_ATTR, | |
565 | attr.span, | |
566 | "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes", | |
567 | "use", | |
568 | "#[rustfmt::skip]".to_string(), | |
569 | Applicability::MachineApplicable, | |
570 | ); | |
571 | } | |
572 | } | |
573 | } | |
574 | ||
575 | fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) { | |
576 | fn find_os(name: &str) -> Option<&'static str> { | |
577 | UNIX_SYSTEMS | |
578 | .iter() | |
579 | .chain(NON_UNIX_SYSTEMS.iter()) | |
580 | .find(|&&os| os == name) | |
581 | .copied() | |
582 | } | |
583 | ||
584 | fn is_unix(name: &str) -> bool { | |
585 | UNIX_SYSTEMS.iter().any(|&os| os == name) | |
586 | } | |
587 | ||
588 | fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> { | |
589 | let mut mismatched = Vec::new(); | |
590 | ||
591 | for item in items { | |
592 | if let NestedMetaItem::MetaItem(meta) = item { | |
593 | match &meta.kind { | |
594 | MetaItemKind::List(list) => { | |
595 | mismatched.extend(find_mismatched_target_os(list)); | |
596 | }, | |
597 | MetaItemKind::Word => { | |
598 | if_chain! { | |
599 | if let Some(ident) = meta.ident(); | |
600 | if let Some(os) = find_os(&*ident.name.as_str()); | |
601 | then { | |
602 | mismatched.push((os, ident.span)); | |
603 | } | |
604 | } | |
605 | }, | |
606 | MetaItemKind::NameValue(..) => {}, | |
607 | } | |
608 | } | |
609 | } | |
610 | ||
611 | mismatched | |
612 | } | |
613 | ||
614 | if_chain! { | |
615 | if attr.has_name(sym::cfg); | |
616 | if let Some(list) = attr.meta_item_list(); | |
617 | let mismatched = find_mismatched_target_os(&list); | |
618 | if !mismatched.is_empty(); | |
619 | then { | |
620 | let mess = "operating system used in target family position"; | |
621 | ||
622 | span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| { | |
623 | // Avoid showing the unix suggestion multiple times in case | |
624 | // we have more than one mismatch for unix-like systems | |
625 | let mut unix_suggested = false; | |
626 | ||
627 | for (os, span) in mismatched { | |
628 | let sugg = format!("target_os = \"{}\"", os); | |
629 | diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect); | |
630 | ||
631 | if !unix_suggested && is_unix(os) { | |
632 | diag.help("did you mean `unix`?"); | |
633 | unix_suggested = true; | |
634 | } | |
635 | } | |
636 | }); | |
637 | } | |
638 | } | |
639 | } | |
640 | ||
641 | fn is_lint_level(symbol: Symbol) -> bool { | |
642 | matches!(symbol, sym::allow | sym::warn | sym::deny | sym::forbid) | |
643 | } |