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