1 //! checks for attributes
2
3 use clippy_utils::macros::{is_panic, macro_backtrace};
4 use clippy_utils::msrvs::{self, Msrv};
5 use clippy_utils::source::{first_line_of_span, is_present_in_source, snippet_opt, without_block_comments};
6 use clippy_utils::{
7 diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then},
8 is_from_proc_macro,
9 };
10 use if_chain::if_chain;
11 use rustc_ast::{AttrKind, AttrStyle, Attribute, LitKind, MetaItemKind, MetaItemLit, NestedMetaItem};
12 use rustc_errors::Applicability;
13 use rustc_hir::{
14 Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind,
15 };
16 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext};
17 use rustc_middle::lint::in_external_macro;
18 use rustc_middle::ty;
19 use rustc_session::{declare_lint_pass, declare_tool_lint, impl_lint_pass};
20 use rustc_span::source_map::Span;
21 use rustc_span::symbol::Symbol;
22 use rustc_span::{sym, DUMMY_SP};
23 use semver::Version;
24
25 static UNIX_SYSTEMS: &[&str] = &[
26 "android",
27 "dragonfly",
28 "emscripten",
29 "freebsd",
30 "fuchsia",
31 "haiku",
32 "illumos",
33 "ios",
34 "l4re",
35 "linux",
36 "macos",
37 "netbsd",
38 "openbsd",
39 "redox",
40 "solaris",
41 "vxworks",
42 ];
43
44 // NOTE: windows is excluded from the list because it's also a valid target family.
45 static NON_UNIX_SYSTEMS: &[&str] = &["hermit", "none", "wasi"];
46
47 declare_clippy_lint! {
48 /// ### What it does
49 /// Checks for items annotated with `#[inline(always)]`,
50 /// unless the annotated function is empty or simply panics.
51 ///
52 /// ### Why is this bad?
53 /// While there are valid uses of this annotation (and once
54 /// you know when to use it, by all means `allow` this lint), it's a common
55 /// newbie-mistake to pepper one's code with it.
56 ///
57 /// As a rule of thumb, before slapping `#[inline(always)]` on a function,
58 /// measure if that additional function call really affects your runtime profile
59 /// sufficiently to make up for the increase in compile time.
60 ///
61 /// ### Known problems
62 /// False positives, big time. This lint is meant to be
63 /// deactivated by everyone doing serious performance work. This means having
64 /// done the measurement.
65 ///
66 /// ### Example
67 /// ```ignore
68 /// #[inline(always)]
69 /// fn not_quite_hot_code(..) { ... }
70 /// ```
71 #[clippy::version = "pre 1.29.0"]
72 pub INLINE_ALWAYS,
73 pedantic,
74 "use of `#[inline(always)]`"
75 }
76
77 declare_clippy_lint! {
78 /// ### What it does
79 /// Checks for `extern crate` and `use` items annotated with
80 /// lint attributes.
81 ///
82 /// This lint permits lint attributes for lints emitted on the items themself.
83 /// For `use` items these lints are:
84 /// * deprecated
85 /// * unreachable_pub
86 /// * unused_imports
87 /// * clippy::enum_glob_use
88 /// * clippy::macro_use_imports
89 /// * clippy::wildcard_imports
90 ///
91 /// For `extern crate` items these lints are:
92 /// * `unused_imports` on items with `#[macro_use]`
93 ///
94 /// ### Why is this bad?
95 /// Lint attributes have no effect on crate imports. Most
96 /// likely a `!` was forgotten.
97 ///
98 /// ### Example
99 /// ```ignore
100 /// #[deny(dead_code)]
101 /// extern crate foo;
102 /// #[forbid(dead_code)]
103 /// use foo::bar;
104 /// ```
105 ///
106 /// Use instead:
107 /// ```rust,ignore
108 /// #[allow(unused_imports)]
109 /// use foo::baz;
110 /// #[allow(unused_imports)]
111 /// #[macro_use]
112 /// extern crate baz;
113 /// ```
114 #[clippy::version = "pre 1.29.0"]
115 pub USELESS_ATTRIBUTE,
116 correctness,
117 "use of lint attributes on `extern crate` items"
118 }
119
120 declare_clippy_lint! {
121 /// ### What it does
122 /// Checks for `#[deprecated]` annotations with a `since`
123 /// field that is not a valid semantic version.
124 ///
125 /// ### Why is this bad?
126 /// For checking the version of the deprecation, it must be
127 /// a valid semver. Failing that, the contained information is useless.
128 ///
129 /// ### Example
130 /// ```rust
131 /// #[deprecated(since = "forever")]
132 /// fn something_else() { /* ... */ }
133 /// ```
134 #[clippy::version = "pre 1.29.0"]
135 pub DEPRECATED_SEMVER,
136 correctness,
137 "use of `#[deprecated(since = \"x\")]` where x is not semver"
138 }
139
140 declare_clippy_lint! {
141 /// ### What it does
142 /// Checks for empty lines after outer attributes
143 ///
144 /// ### Why is this bad?
145 /// Most likely the attribute was meant to be an inner attribute using a '!'.
146 /// If it was meant to be an outer attribute, then the following item
147 /// should not be separated by empty lines.
148 ///
149 /// ### Known problems
150 /// Can cause false positives.
151 ///
152 /// From the clippy side it's difficult to detect empty lines between an attributes and the
153 /// following item because empty lines and comments are not part of the AST. The parsing
154 /// currently works for basic cases but is not perfect.
155 ///
156 /// ### Example
157 /// ```rust
158 /// #[allow(dead_code)]
159 ///
160 /// fn not_quite_good_code() { }
161 /// ```
162 ///
163 /// Use instead:
164 /// ```rust
165 /// // Good (as inner attribute)
166 /// #![allow(dead_code)]
167 ///
168 /// fn this_is_fine() { }
169 ///
170 /// // or
171 ///
172 /// // Good (as outer attribute)
173 /// #[allow(dead_code)]
174 /// fn this_is_fine_too() { }
175 /// ```
176 #[clippy::version = "pre 1.29.0"]
177 pub EMPTY_LINE_AFTER_OUTER_ATTR,
178 nursery,
179 "empty line after outer attribute"
180 }
181
182 declare_clippy_lint! {
183 /// ### What it does
184 /// Checks for empty lines after documenation comments.
185 ///
186 /// ### Why is this bad?
187 /// The documentation comment was most likely meant to be an inner attribute or regular comment.
188 /// If it was intended to be a documentation comment, then the empty line should be removed to
189 /// be more idiomatic.
190 ///
191 /// ### Known problems
192 /// Only detects empty lines immediately following the documentation. If the doc comment is followed
193 /// by an attribute and then an empty line, this lint will not trigger. Use `empty_line_after_outer_attr`
194 /// in combination with this lint to detect both cases.
195 ///
196 /// Does not detect empty lines after doc attributes (e.g. `#[doc = ""]`).
197 ///
198 /// ### Example
199 /// ```rust
200 /// /// Some doc comment with a blank line after it.
201 ///
202 /// fn not_quite_good_code() { }
203 /// ```
204 ///
205 /// Use instead:
206 /// ```rust
207 /// /// Good (no blank line)
208 /// fn this_is_fine() { }
209 /// ```
210 ///
211 /// ```rust
212 /// // Good (convert to a regular comment)
213 ///
214 /// fn this_is_fine_too() { }
215 /// ```
216 ///
217 /// ```rust
218 /// //! Good (convert to a comment on an inner attribute)
219 ///
220 /// fn this_is_fine_as_well() { }
221 /// ```
222 #[clippy::version = "1.70.0"]
223 pub EMPTY_LINE_AFTER_DOC_COMMENTS,
224 nursery,
225 "empty line after documentation comments"
226 }
227
228 declare_clippy_lint! {
229 /// ### What it does
230 /// Checks for `warn`/`deny`/`forbid` attributes targeting the whole clippy::restriction category.
231 ///
232 /// ### Why is this bad?
233 /// Restriction lints sometimes are in contrast with other lints or even go against idiomatic rust.
234 /// These lints should only be enabled on a lint-by-lint basis and with careful consideration.
235 ///
236 /// ### Example
237 /// ```rust
238 /// #![deny(clippy::restriction)]
239 /// ```
240 ///
241 /// Use instead:
242 /// ```rust
243 /// #![deny(clippy::as_conversions)]
244 /// ```
245 #[clippy::version = "1.47.0"]
246 pub BLANKET_CLIPPY_RESTRICTION_LINTS,
247 suspicious,
248 "enabling the complete restriction group"
249 }
250
251 declare_clippy_lint! {
252 /// ### What it does
253 /// Checks for `#[cfg_attr(rustfmt, rustfmt_skip)]` and suggests to replace it
254 /// with `#[rustfmt::skip]`.
255 ///
256 /// ### Why is this bad?
257 /// Since tool_attributes ([rust-lang/rust#44690](https://github.com/rust-lang/rust/issues/44690))
258 /// are stable now, they should be used instead of the old `cfg_attr(rustfmt)` attributes.
259 ///
260 /// ### Known problems
261 /// This lint doesn't detect crate level inner attributes, because they get
262 /// processed before the PreExpansionPass lints get executed. See
263 /// [#3123](https://github.com/rust-lang/rust-clippy/pull/3123#issuecomment-422321765)
264 ///
265 /// ### Example
266 /// ```rust
267 /// #[cfg_attr(rustfmt, rustfmt_skip)]
268 /// fn main() { }
269 /// ```
270 ///
271 /// Use instead:
272 /// ```rust
273 /// #[rustfmt::skip]
274 /// fn main() { }
275 /// ```
276 #[clippy::version = "1.32.0"]
277 pub DEPRECATED_CFG_ATTR,
278 complexity,
279 "usage of `cfg_attr(rustfmt)` instead of tool attributes"
280 }
281
282 declare_clippy_lint! {
283 /// ### What it does
284 /// Checks for cfg attributes having operating systems used in target family position.
285 ///
286 /// ### Why is this bad?
287 /// The configuration option will not be recognised and the related item will not be included
288 /// by the conditional compilation engine.
289 ///
290 /// ### Example
291 /// ```rust
292 /// #[cfg(linux)]
293 /// fn conditional() { }
294 /// ```
295 ///
296 /// Use instead:
297 /// ```rust
298 /// # mod hidden {
299 /// #[cfg(target_os = "linux")]
300 /// fn conditional() { }
301 /// # }
302 ///
303 /// // or
304 ///
305 /// #[cfg(unix)]
306 /// fn conditional() { }
307 /// ```
308 /// Check the [Rust Reference](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os) for more details.
309 #[clippy::version = "1.45.0"]
310 pub MISMATCHED_TARGET_OS,
311 correctness,
312 "usage of `cfg(operating_system)` instead of `cfg(target_os = \"operating_system\")`"
313 }
314
315 declare_clippy_lint! {
316 /// ### What it does
317 /// Checks for attributes that allow lints without a reason.
318 ///
319 /// (This requires the `lint_reasons` feature)
320 ///
321 /// ### Why is this bad?
322 /// Allowing a lint should always have a reason. This reason should be documented to
323 /// ensure that others understand the reasoning
324 ///
325 /// ### Example
326 /// ```rust
327 /// #![feature(lint_reasons)]
328 ///
329 /// #![allow(clippy::some_lint)]
330 /// ```
331 ///
332 /// Use instead:
333 /// ```rust
334 /// #![feature(lint_reasons)]
335 ///
336 /// #![allow(clippy::some_lint, reason = "False positive rust-lang/rust-clippy#1002020")]
337 /// ```
338 #[clippy::version = "1.61.0"]
339 pub ALLOW_ATTRIBUTES_WITHOUT_REASON,
340 restriction,
341 "ensures that all `allow` and `expect` attributes have a reason"
342 }
343
344 declare_clippy_lint! {
345 /// ### What it does
346 /// Checks for `any` and `all` combinators in `cfg` with only one condition.
347 ///
348 /// ### Why is this bad?
349 /// If there is only one condition, no need to wrap it into `any` or `all` combinators.
350 ///
351 /// ### Example
352 /// ```rust
353 /// #[cfg(any(unix))]
354 /// pub struct Bar;
355 /// ```
356 ///
357 /// Use instead:
358 /// ```rust
359 /// #[cfg(unix)]
360 /// pub struct Bar;
361 /// ```
362 #[clippy::version = "1.71.0"]
363 pub NON_MINIMAL_CFG,
364 style,
365 "ensure that all `cfg(any())` and `cfg(all())` have more than one condition"
366 }
367
368 declare_clippy_lint! {
369 /// ### What it does
370 /// Checks for `#[cfg(features = "...")]` and suggests to replace it with
371 /// `#[cfg(feature = "...")]`.
372 ///
373 /// ### Why is this bad?
374 /// Misspelling `feature` as `features` can be sometimes hard to spot. It
375 /// may cause conditional compilation not work quitely.
376 ///
377 /// ### Example
378 /// ```rust
379 /// #[cfg(features = "some-feature")]
380 /// fn conditional() { }
381 /// ```
382 ///
383 /// Use instead:
384 /// ```rust
385 /// #[cfg(feature = "some-feature")]
386 /// fn conditional() { }
387 /// ```
388 #[clippy::version = "1.69.0"]
389 pub MAYBE_MISUSED_CFG,
390 suspicious,
391 "prevent from misusing the wrong attr name"
392 }
393
394 declare_lint_pass!(Attributes => [
395 ALLOW_ATTRIBUTES_WITHOUT_REASON,
396 INLINE_ALWAYS,
397 DEPRECATED_SEMVER,
398 USELESS_ATTRIBUTE,
399 BLANKET_CLIPPY_RESTRICTION_LINTS,
400 ]);
401
402 impl<'tcx> LateLintPass<'tcx> for Attributes {
check_crate(&mut self, cx: &LateContext<'tcx>)403 fn check_crate(&mut self, cx: &LateContext<'tcx>) {
404 for (name, level) in &cx.sess().opts.lint_opts {
405 if name == "clippy::restriction" && *level > Level::Allow {
406 span_lint_and_then(
407 cx,
408 BLANKET_CLIPPY_RESTRICTION_LINTS,
409 DUMMY_SP,
410 "`clippy::restriction` is not meant to be enabled as a group",
411 |diag| {
412 diag.note(format!(
413 "because of the command line `--{} clippy::restriction`",
414 level.as_str()
415 ));
416 diag.help("enable the restriction lints you need individually");
417 },
418 );
419 }
420 }
421 }
422
check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute)423 fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
424 if let Some(items) = &attr.meta_item_list() {
425 if let Some(ident) = attr.ident() {
426 if is_lint_level(ident.name) {
427 check_clippy_lint_names(cx, ident.name, items);
428 }
429 if matches!(ident.name, sym::allow | sym::expect) {
430 check_lint_reason(cx, ident.name, items, attr);
431 }
432 if items.is_empty() || !attr.has_name(sym::deprecated) {
433 return;
434 }
435 for item in items {
436 if_chain! {
437 if let NestedMetaItem::MetaItem(mi) = &item;
438 if let MetaItemKind::NameValue(lit) = &mi.kind;
439 if mi.has_name(sym::since);
440 then {
441 check_semver(cx, item.span(), lit);
442 }
443 }
444 }
445 }
446 }
447 }
448
check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>)449 fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
450 let attrs = cx.tcx.hir().attrs(item.hir_id());
451 if is_relevant_item(cx, item) {
452 check_attrs(cx, item.span, item.ident.name, attrs);
453 }
454 match item.kind {
455 ItemKind::ExternCrate(..) | ItemKind::Use(..) => {
456 let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use));
457
458 for attr in attrs {
459 if in_external_macro(cx.sess(), attr.span) {
460 return;
461 }
462 if let Some(lint_list) = &attr.meta_item_list() {
463 if attr.ident().map_or(false, |ident| is_lint_level(ident.name)) {
464 for lint in lint_list {
465 match item.kind {
466 ItemKind::Use(..) => {
467 if is_word(lint, sym::unused_imports)
468 || is_word(lint, sym::deprecated)
469 || is_word(lint, sym!(unreachable_pub))
470 || is_word(lint, sym!(unused))
471 || extract_clippy_lint(lint).map_or(false, |s| {
472 matches!(
473 s.as_str(),
474 "wildcard_imports"
475 | "enum_glob_use"
476 | "redundant_pub_crate"
477 | "macro_use_imports"
478 | "unsafe_removed_from_name"
479 | "module_name_repetitions"
480 | "single_component_path_imports"
481 )
482 })
483 {
484 return;
485 }
486 },
487 ItemKind::ExternCrate(..) => {
488 if is_word(lint, sym::unused_imports) && skip_unused_imports {
489 return;
490 }
491 if is_word(lint, sym!(unused_extern_crates)) {
492 return;
493 }
494 },
495 _ => {},
496 }
497 }
498 let line_span = first_line_of_span(cx, attr.span);
499
500 if let Some(mut sugg) = snippet_opt(cx, line_span) {
501 if sugg.contains("#[") {
502 span_lint_and_then(
503 cx,
504 USELESS_ATTRIBUTE,
505 line_span,
506 "useless lint attribute",
507 |diag| {
508 sugg = sugg.replacen("#[", "#![", 1);
509 diag.span_suggestion(
510 line_span,
511 "if you just forgot a `!`, use",
512 sugg,
513 Applicability::MaybeIncorrect,
514 );
515 },
516 );
517 }
518 }
519 }
520 }
521 }
522 },
523 _ => {},
524 }
525 }
526
check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>)527 fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
528 if is_relevant_impl(cx, item) {
529 check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
530 }
531 }
532
check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>)533 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
534 if is_relevant_trait(cx, item) {
535 check_attrs(cx, item.span, item.ident.name, cx.tcx.hir().attrs(item.hir_id()));
536 }
537 }
538 }
539
540 /// Returns the lint name if it is clippy lint.
extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol>541 fn extract_clippy_lint(lint: &NestedMetaItem) -> Option<Symbol> {
542 if_chain! {
543 if let Some(meta_item) = lint.meta_item();
544 if meta_item.path.segments.len() > 1;
545 if let tool_name = meta_item.path.segments[0].ident;
546 if tool_name.name == sym::clippy;
547 then {
548 let lint_name = meta_item.path.segments.last().unwrap().ident.name;
549 return Some(lint_name);
550 }
551 }
552 None
553 }
554
check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem])555 fn check_clippy_lint_names(cx: &LateContext<'_>, name: Symbol, items: &[NestedMetaItem]) {
556 for lint in items {
557 if let Some(lint_name) = extract_clippy_lint(lint) {
558 if lint_name.as_str() == "restriction" && name != sym::allow {
559 span_lint_and_help(
560 cx,
561 BLANKET_CLIPPY_RESTRICTION_LINTS,
562 lint.span(),
563 "`clippy::restriction` is not meant to be enabled as a group",
564 None,
565 "enable the restriction lints you need individually",
566 );
567 }
568 }
569 }
570 }
571
check_lint_reason<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMetaItem], attr: &'cx Attribute)572 fn check_lint_reason<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMetaItem], attr: &'cx Attribute) {
573 // Check for the feature
574 if !cx.tcx.features().lint_reasons {
575 return;
576 }
577
578 // Check if the reason is present
579 if let Some(item) = items.last().and_then(NestedMetaItem::meta_item)
580 && let MetaItemKind::NameValue(_) = &item.kind
581 && item.path == sym::reason
582 {
583 return;
584 }
585
586 // Check if the attribute is in an external macro and therefore out of the developer's control
587 if in_external_macro(cx.sess(), attr.span) || is_from_proc_macro(cx, &attr) {
588 return;
589 }
590
591 span_lint_and_help(
592 cx,
593 ALLOW_ATTRIBUTES_WITHOUT_REASON,
594 attr.span,
595 &format!("`{}` attribute without specifying a reason", name.as_str()),
596 None,
597 "try adding a reason at the end with `, reason = \"..\"`",
598 );
599 }
600
is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool601 fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
602 if let ItemKind::Fn(_, _, eid) = item.kind {
603 is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value)
604 } else {
605 true
606 }
607 }
608
is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool609 fn is_relevant_impl(cx: &LateContext<'_>, item: &ImplItem<'_>) -> bool {
610 match item.kind {
611 ImplItemKind::Fn(_, eid) => is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value),
612 _ => false,
613 }
614 }
615
is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool616 fn is_relevant_trait(cx: &LateContext<'_>, item: &TraitItem<'_>) -> bool {
617 match item.kind {
618 TraitItemKind::Fn(_, TraitFn::Required(_)) => true,
619 TraitItemKind::Fn(_, TraitFn::Provided(eid)) => {
620 is_relevant_expr(cx, cx.tcx.typeck_body(eid), cx.tcx.hir().body(eid).value)
621 },
622 _ => false,
623 }
624 }
625
is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool626 fn is_relevant_block(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, block: &Block<'_>) -> bool {
627 block.stmts.first().map_or(
628 block
629 .expr
630 .as_ref()
631 .map_or(false, |e| is_relevant_expr(cx, typeck_results, e)),
632 |stmt| match &stmt.kind {
633 StmtKind::Local(_) => true,
634 StmtKind::Expr(expr) | StmtKind::Semi(expr) => is_relevant_expr(cx, typeck_results, expr),
635 StmtKind::Item(_) => false,
636 },
637 )
638 }
639
is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool640 fn is_relevant_expr(cx: &LateContext<'_>, typeck_results: &ty::TypeckResults<'_>, expr: &Expr<'_>) -> bool {
641 if macro_backtrace(expr.span).last().map_or(false, |macro_call| {
642 is_panic(cx, macro_call.def_id) || cx.tcx.item_name(macro_call.def_id) == sym::unreachable
643 }) {
644 return false;
645 }
646 match &expr.kind {
647 ExprKind::Block(block, _) => is_relevant_block(cx, typeck_results, block),
648 ExprKind::Ret(Some(e)) => is_relevant_expr(cx, typeck_results, e),
649 ExprKind::Ret(None) | ExprKind::Break(_, None) => false,
650 _ => true,
651 }
652 }
653
check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute])654 fn check_attrs(cx: &LateContext<'_>, span: Span, name: Symbol, attrs: &[Attribute]) {
655 if span.from_expansion() {
656 return;
657 }
658
659 for attr in attrs {
660 if let Some(values) = attr.meta_item_list() {
661 if values.len() != 1 || !attr.has_name(sym::inline) {
662 continue;
663 }
664 if is_word(&values[0], sym::always) {
665 span_lint(
666 cx,
667 INLINE_ALWAYS,
668 attr.span,
669 &format!("you have declared `#[inline(always)]` on `{name}`. This is usually a bad idea"),
670 );
671 }
672 }
673 }
674 }
675
check_semver(cx: &LateContext<'_>, span: Span, lit: &MetaItemLit)676 fn check_semver(cx: &LateContext<'_>, span: Span, lit: &MetaItemLit) {
677 if let LitKind::Str(is, _) = lit.kind {
678 if Version::parse(is.as_str()).is_ok() {
679 return;
680 }
681 }
682 span_lint(
683 cx,
684 DEPRECATED_SEMVER,
685 span,
686 "the since field must contain a semver-compliant version",
687 );
688 }
689
is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool690 fn is_word(nmi: &NestedMetaItem, expected: Symbol) -> bool {
691 if let NestedMetaItem::MetaItem(mi) = &nmi {
692 mi.is_word() && mi.has_name(expected)
693 } else {
694 false
695 }
696 }
697
698 pub struct EarlyAttributes {
699 pub msrv: Msrv,
700 }
701
702 impl_lint_pass!(EarlyAttributes => [
703 DEPRECATED_CFG_ATTR,
704 MISMATCHED_TARGET_OS,
705 EMPTY_LINE_AFTER_OUTER_ATTR,
706 EMPTY_LINE_AFTER_DOC_COMMENTS,
707 NON_MINIMAL_CFG,
708 MAYBE_MISUSED_CFG,
709 ]);
710
711 impl EarlyLintPass for EarlyAttributes {
check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item)712 fn check_item(&mut self, cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
713 check_empty_line_after_outer_attr(cx, item);
714 }
715
check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute)716 fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
717 check_deprecated_cfg_attr(cx, attr, &self.msrv);
718 check_mismatched_target_os(cx, attr);
719 check_minimal_cfg_condition(cx, attr);
720 check_misused_cfg(cx, attr);
721 }
722
723 extract_msrv_attr!(EarlyContext);
724 }
725
726 /// Check for empty lines after outer attributes.
727 ///
728 /// Attributes and documenation comments are both considered outer attributes
729 /// by the AST. However, the average user likely considers them to be different.
730 /// Checking for empty lines after each of these attributes is split into two different
731 /// lints but can share the same logic.
check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item)732 fn check_empty_line_after_outer_attr(cx: &EarlyContext<'_>, item: &rustc_ast::Item) {
733 let mut iter = item.attrs.iter().peekable();
734 while let Some(attr) = iter.next() {
735 if (matches!(attr.kind, AttrKind::Normal(..)) || matches!(attr.kind, AttrKind::DocComment(..)))
736 && attr.style == AttrStyle::Outer
737 && is_present_in_source(cx, attr.span)
738 {
739 let begin_of_attr_to_item = Span::new(attr.span.lo(), item.span.lo(), item.span.ctxt(), item.span.parent());
740 let end_of_attr_to_next_attr_or_item = Span::new(
741 attr.span.hi(),
742 iter.peek().map_or(item.span.lo(), |next_attr| next_attr.span.lo()),
743 item.span.ctxt(),
744 item.span.parent(),
745 );
746
747 if let Some(snippet) = snippet_opt(cx, end_of_attr_to_next_attr_or_item) {
748 let lines = snippet.split('\n').collect::<Vec<_>>();
749 let lines = without_block_comments(lines);
750
751 if lines.iter().filter(|l| l.trim().is_empty()).count() > 2 {
752 let (lint_msg, lint_type) = match attr.kind {
753 AttrKind::DocComment(..) => (
754 "found an empty line after a doc comment. \
755 Perhaps you need to use `//!` to make a comment on a module, remove the empty line, or make a regular comment with `//`?",
756 EMPTY_LINE_AFTER_DOC_COMMENTS,
757 ),
758 AttrKind::Normal(..) => (
759 "found an empty line after an outer attribute. \
760 Perhaps you forgot to add a `!` to make it an inner attribute?",
761 EMPTY_LINE_AFTER_OUTER_ATTR,
762 ),
763 };
764
765 span_lint(cx, lint_type, begin_of_attr_to_item, lint_msg);
766 }
767 }
768 }
769 }
770 }
771
check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv)772 fn check_deprecated_cfg_attr(cx: &EarlyContext<'_>, attr: &Attribute, msrv: &Msrv) {
773 if_chain! {
774 if msrv.meets(msrvs::TOOL_ATTRIBUTES);
775 // check cfg_attr
776 if attr.has_name(sym::cfg_attr);
777 if let Some(items) = attr.meta_item_list();
778 if items.len() == 2;
779 // check for `rustfmt`
780 if let Some(feature_item) = items[0].meta_item();
781 if feature_item.has_name(sym::rustfmt);
782 // check for `rustfmt_skip` and `rustfmt::skip`
783 if let Some(skip_item) = &items[1].meta_item();
784 if skip_item.has_name(sym!(rustfmt_skip))
785 || skip_item
786 .path
787 .segments
788 .last()
789 .expect("empty path in attribute")
790 .ident
791 .name
792 == sym::skip;
793 // Only lint outer attributes, because custom inner attributes are unstable
794 // Tracking issue: https://github.com/rust-lang/rust/issues/54726
795 if attr.style == AttrStyle::Outer;
796 then {
797 span_lint_and_sugg(
798 cx,
799 DEPRECATED_CFG_ATTR,
800 attr.span,
801 "`cfg_attr` is deprecated for rustfmt and got replaced by tool attributes",
802 "use",
803 "#[rustfmt::skip]".to_string(),
804 Applicability::MachineApplicable,
805 );
806 }
807 }
808 }
809
check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem])810 fn check_nested_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
811 for item in items {
812 if let NestedMetaItem::MetaItem(meta) = item {
813 if !meta.has_name(sym::any) && !meta.has_name(sym::all) {
814 continue;
815 }
816 if let MetaItemKind::List(list) = &meta.kind {
817 check_nested_cfg(cx, list);
818 if list.len() == 1 {
819 span_lint_and_then(
820 cx,
821 NON_MINIMAL_CFG,
822 meta.span,
823 "unneeded sub `cfg` when there is only one condition",
824 |diag| {
825 if let Some(snippet) = snippet_opt(cx, list[0].span()) {
826 diag.span_suggestion(meta.span, "try", snippet, Applicability::MaybeIncorrect);
827 }
828 },
829 );
830 } else if list.is_empty() && meta.has_name(sym::all) {
831 span_lint_and_then(
832 cx,
833 NON_MINIMAL_CFG,
834 meta.span,
835 "unneeded sub `cfg` when there is no condition",
836 |_| {},
837 );
838 }
839 }
840 }
841 }
842 }
843
check_nested_misused_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem])844 fn check_nested_misused_cfg(cx: &EarlyContext<'_>, items: &[NestedMetaItem]) {
845 for item in items {
846 if let NestedMetaItem::MetaItem(meta) = item {
847 if meta.has_name(sym!(features)) && let Some(val) = meta.value_str() {
848 span_lint_and_sugg(
849 cx,
850 MAYBE_MISUSED_CFG,
851 meta.span,
852 "feature may misspelled as features",
853 "use",
854 format!("feature = \"{val}\""),
855 Applicability::MaybeIncorrect,
856 );
857 }
858 if let MetaItemKind::List(list) = &meta.kind {
859 check_nested_misused_cfg(cx, list);
860 }
861 }
862 }
863 }
864
check_minimal_cfg_condition(cx: &EarlyContext<'_>, attr: &Attribute)865 fn check_minimal_cfg_condition(cx: &EarlyContext<'_>, attr: &Attribute) {
866 if attr.has_name(sym::cfg) &&
867 let Some(items) = attr.meta_item_list()
868 {
869 check_nested_cfg(cx, &items);
870 }
871 }
872
check_misused_cfg(cx: &EarlyContext<'_>, attr: &Attribute)873 fn check_misused_cfg(cx: &EarlyContext<'_>, attr: &Attribute) {
874 if attr.has_name(sym::cfg) &&
875 let Some(items) = attr.meta_item_list()
876 {
877 check_nested_misused_cfg(cx, &items);
878 }
879 }
880
check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute)881 fn check_mismatched_target_os(cx: &EarlyContext<'_>, attr: &Attribute) {
882 fn find_os(name: &str) -> Option<&'static str> {
883 UNIX_SYSTEMS
884 .iter()
885 .chain(NON_UNIX_SYSTEMS.iter())
886 .find(|&&os| os == name)
887 .copied()
888 }
889
890 fn is_unix(name: &str) -> bool {
891 UNIX_SYSTEMS.iter().any(|&os| os == name)
892 }
893
894 fn find_mismatched_target_os(items: &[NestedMetaItem]) -> Vec<(&str, Span)> {
895 let mut mismatched = Vec::new();
896
897 for item in items {
898 if let NestedMetaItem::MetaItem(meta) = item {
899 match &meta.kind {
900 MetaItemKind::List(list) => {
901 mismatched.extend(find_mismatched_target_os(list));
902 },
903 MetaItemKind::Word => {
904 if_chain! {
905 if let Some(ident) = meta.ident();
906 if let Some(os) = find_os(ident.name.as_str());
907 then {
908 mismatched.push((os, ident.span));
909 }
910 }
911 },
912 MetaItemKind::NameValue(..) => {},
913 }
914 }
915 }
916
917 mismatched
918 }
919
920 if_chain! {
921 if attr.has_name(sym::cfg);
922 if let Some(list) = attr.meta_item_list();
923 let mismatched = find_mismatched_target_os(&list);
924 if !mismatched.is_empty();
925 then {
926 let mess = "operating system used in target family position";
927
928 span_lint_and_then(cx, MISMATCHED_TARGET_OS, attr.span, mess, |diag| {
929 // Avoid showing the unix suggestion multiple times in case
930 // we have more than one mismatch for unix-like systems
931 let mut unix_suggested = false;
932
933 for (os, span) in mismatched {
934 let sugg = format!("target_os = \"{os}\"");
935 diag.span_suggestion(span, "try", sugg, Applicability::MaybeIncorrect);
936
937 if !unix_suggested && is_unix(os) {
938 diag.help("did you mean `unix`?");
939 unix_suggested = true;
940 }
941 }
942 });
943 }
944 }
945 }
946
is_lint_level(symbol: Symbol) -> bool947 fn is_lint_level(symbol: Symbol) -> bool {
948 matches!(symbol, sym::allow | sym::expect | sym::warn | sym::deny | sym::forbid)
949 }
950