about summary refs log tree commit diff
path: root/clippy_lints/src/absolute_paths.rs
blob: c0a9d888e0b0bd04c74be648cbc8380106d7c99a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet_opt;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX};
use rustc_hir::{HirId, ItemKind, Node, Path};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::symbol::kw;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for usage of items through absolute paths, like `std::env::current_dir`.
    ///
    /// ### Why restrict this?
    /// Many codebases have their own style when it comes to importing, but one that is seldom used
    /// is using absolute paths *everywhere*. This is generally considered unidiomatic, and you
    /// should add a `use` statement.
    ///
    /// The default maximum segments (2) is pretty strict, you may want to increase this in
    /// `clippy.toml`.
    ///
    /// Note: One exception to this is code from macro expansion - this does not lint such cases, as
    /// using absolute paths is the proper way of referencing items in one.
    ///
    /// ### Example
    /// ```no_run
    /// let x = std::f64::consts::PI;
    /// ```
    /// Use any of the below instead, or anything else:
    /// ```no_run
    /// use std::f64;
    /// use std::f64::consts;
    /// use std::f64::consts::PI;
    /// let x = f64::consts::PI;
    /// let x = consts::PI;
    /// let x = PI;
    /// use std::f64::consts as f64_consts;
    /// let x = f64_consts::PI;
    /// ```
    #[clippy::version = "1.73.0"]
    pub ABSOLUTE_PATHS,
    restriction,
    "checks for usage of an item without a `use` statement"
}
impl_lint_pass!(AbsolutePaths => [ABSOLUTE_PATHS]);

pub struct AbsolutePaths {
    pub absolute_paths_max_segments: u64,
    pub absolute_paths_allowed_crates: &'static FxHashSet<String>,
}

impl AbsolutePaths {
    pub fn new(conf: &'static Conf) -> Self {
        Self {
            absolute_paths_max_segments: conf.absolute_paths_max_segments,
            absolute_paths_allowed_crates: &conf.absolute_paths_allowed_crates,
        }
    }
}

impl LateLintPass<'_> for AbsolutePaths {
    // We should only lint `QPath::Resolved`s, but since `Path` is only used in `Resolved` and `UsePath`
    // we don't need to use a visitor or anything as we can just check if the `Node` for `hir_id` isn't
    // a `Use`
    #[expect(clippy::cast_possible_truncation)]
    fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, hir_id: HirId) {
        let Self {
            absolute_paths_max_segments,
            absolute_paths_allowed_crates,
        } = self;

        if !path.span.from_expansion()
            && let node = cx.tcx.hir_node(hir_id)
            && !matches!(node, Node::Item(item) if matches!(item.kind, ItemKind::Use(_, _)))
            && let [first, rest @ ..] = path.segments
            // Handle `::std`
            && let (segment, len) = if first.ident.name == kw::PathRoot {
                // Indexing is fine as `PathRoot` must be followed by another segment. `len() - 1`
                // is fine here for the same reason
                (&rest[0], path.segments.len() - 1)
            } else {
                (first, path.segments.len())
            }
            && len > *absolute_paths_max_segments as usize
            && let Some(segment_snippet) = snippet_opt(cx, segment.ident.span)
            && segment_snippet == segment.ident.as_str()
        {
            let is_abs_external =
                matches!(segment.res, Res::Def(DefKind::Mod, DefId { index, .. }) if index == CRATE_DEF_INDEX);
            let is_abs_crate = segment.ident.name == kw::Crate;

            if is_abs_external && absolute_paths_allowed_crates.contains(segment.ident.name.as_str())
                || is_abs_crate && absolute_paths_allowed_crates.contains("crate")
            {
                return;
            }

            if is_abs_external || is_abs_crate {
                span_lint(
                    cx,
                    ABSOLUTE_PATHS,
                    path.span,
                    "consider bringing this path into scope with the `use` keyword",
                );
            }
        }
    }
}