|
| 1 | +use clippy_utils::diagnostics::span_lint; |
| 2 | +use clippy_utils::source::snippet_opt; |
| 3 | +use rustc_data_structures::fx::FxHashSet; |
| 4 | +use rustc_hir::def::{DefKind, Res}; |
| 5 | +use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX}; |
| 6 | +use rustc_hir::{HirId, ItemKind, Node, Path}; |
| 7 | +use rustc_lint::{LateContext, LateLintPass}; |
| 8 | +use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 9 | +use rustc_span::symbol::kw; |
| 10 | + |
| 11 | +declare_clippy_lint! { |
| 12 | + /// ### What it does |
| 13 | + /// Checks for usage of items through absolute paths, like `std::env::current_dir`. |
| 14 | + /// |
| 15 | + /// ### Why is this bad? |
| 16 | + /// Many codebases have their own style when it comes to importing, but one that is seldom used |
| 17 | + /// is using absolute paths *everywhere*. This is generally considered unidiomatic, and you |
| 18 | + /// should add a `use` statement. |
| 19 | + /// |
| 20 | + /// The default maximum segments (2) is pretty strict, you may want to increase this in |
| 21 | + /// `clippy.toml`. |
| 22 | + /// |
| 23 | + /// Note: One exception to this is code from macro expansion - this does not lint such cases, as |
| 24 | + /// using absolute paths is the proper way of referencing items in one. |
| 25 | + /// |
| 26 | + /// ### Example |
| 27 | + /// ```rust |
| 28 | + /// let x = std::f64::consts::PI; |
| 29 | + /// ``` |
| 30 | + /// Use any of the below instead, or anything else: |
| 31 | + /// ```rust |
| 32 | + /// use std::f64; |
| 33 | + /// use std::f64::consts; |
| 34 | + /// use std::f64::consts::PI; |
| 35 | + /// let x = f64::consts::PI; |
| 36 | + /// let x = consts::PI; |
| 37 | + /// let x = PI; |
| 38 | + /// use std::f64::consts as f64_consts; |
| 39 | + /// let x = f64_consts::PI; |
| 40 | + /// ``` |
| 41 | + #[clippy::version = "1.73.0"] |
| 42 | + pub ABSOLUTE_PATHS, |
| 43 | + restriction, |
| 44 | + "checks for usage of an item without a `use` statement" |
| 45 | +} |
| 46 | +impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]); |
| 47 | + |
| 48 | +pub struct AbsolutePaths { |
| 49 | + pub absolute_paths_max_segments: u64, |
| 50 | + pub absolute_paths_allowed_crates: FxHashSet<String>, |
| 51 | +} |
| 52 | + |
| 53 | +impl LateLintPass<'_> for AbsolutePaths { |
| 54 | + // We should only lint `QPath::Resolved`s, but since `Path` is only used in `Resolved` and `UsePath` |
| 55 | + // we don't need to use a visitor or anything as we can just check if the `Node` for `hir_id` isn't |
| 56 | + // a `Use` |
| 57 | + #[expect(clippy::cast_possible_truncation)] |
| 58 | + fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) { |
| 59 | + let Self { |
| 60 | + absolute_paths_max_segments, |
| 61 | + absolute_paths_allowed_crates, |
| 62 | + } = self; |
| 63 | + |
| 64 | + if !path.span.from_expansion() |
| 65 | + && let Some(node) = cx.tcx.hir().find(hir_id) |
| 66 | + && !matches!(node, Node::Item(item) if matches!(item.kind, ItemKind::Use(_, _))) |
| 67 | + && let [first, rest @ ..] = path.segments |
| 68 | + // Handle `::std` |
| 69 | + && let (segment, len) = if first.ident.name == kw::PathRoot { |
| 70 | + // Indexing is fine as `PathRoot` must be followed by another segment. `len() - 1` |
| 71 | + // is fine here for the same reason |
| 72 | + (&rest[0], path.segments.len() - 1) |
| 73 | + } else { |
| 74 | + (first, path.segments.len()) |
| 75 | + } |
| 76 | + && len > *absolute_paths_max_segments as usize |
| 77 | + && let Some(segment_snippet) = snippet_opt(cx, segment.ident.span) |
| 78 | + && segment_snippet == segment.ident.as_str() |
| 79 | + { |
| 80 | + let is_abs_external = |
| 81 | + matches!(segment.res, Res::Def(DefKind::Mod, DefId { index, .. }) if index == CRATE_DEF_INDEX); |
| 82 | + let is_abs_crate = segment.ident.name == kw::Crate; |
| 83 | + |
| 84 | + if is_abs_external && absolute_paths_allowed_crates.contains(segment.ident.name.as_str()) |
| 85 | + || is_abs_crate && absolute_paths_allowed_crates.contains("crate") |
| 86 | + { |
| 87 | + return; |
| 88 | + } |
| 89 | + |
| 90 | + if is_abs_external || is_abs_crate { |
| 91 | + span_lint( |
| 92 | + cx, |
| 93 | + ABSOLUTE_PATHS, |
| 94 | + path.span, |
| 95 | + "consider bringing this path into scope with the `use` keyword", |
| 96 | + ); |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | +} |
0 commit comments