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