2023-07-31 23:53:53 +02:00
|
|
|
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};
|
2023-12-01 18:21:58 +01:00
|
|
|
use rustc_session::impl_lint_pass;
|
2023-07-31 23:53:53 +02:00
|
|
|
use rustc_span::symbol::kw;
|
|
|
|
|
|
|
|
declare_clippy_lint! {
|
|
|
|
/// ### What it does
|
|
|
|
/// Checks for usage of items through absolute paths, like `std::env::current_dir`.
|
|
|
|
///
|
For restriction lints, replace “Why is this bad?” with “Why restrict this?”
The `restriction` group contains many lints which are not about
necessarily “bad” things, but style choices — perhaps even style choices
which contradict conventional Rust style — or are otherwise very
situational. This results in silly wording like “Why is this bad?
It isn't, but ...”, which I’ve seen confuse a newcomer at least once.
To improve this situation, this commit replaces the “Why is this bad?”
section heading with “Why restrict this?”, for most, but not all,
restriction lints. I left alone the ones whose placement in the
restriction group is more incidental.
In order to make this make sense, I had to remove the “It isn't, but”
texts from the contents of the sections. Sometimes further changes
were needed, or there were obvious fixes to make, and I went ahead
and made those changes without attempting to split them into another
commit, even though many of them are not strictly necessary for the
“Why restrict this?” project.
2024-05-22 22:21:01 -07:00
|
|
|
/// ### Why restrict this?
|
2023-07-31 23:53:53 +02:00
|
|
|
/// 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
|
2023-11-02 17:35:56 +01:00
|
|
|
/// ```no_run
|
2023-07-31 23:53:53 +02:00
|
|
|
/// let x = std::f64::consts::PI;
|
|
|
|
/// ```
|
|
|
|
/// Use any of the below instead, or anything else:
|
2023-11-02 17:35:56 +01:00
|
|
|
/// ```no_run
|
2023-07-31 23:53:53 +02:00
|
|
|
/// 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: FxHashSet<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
2024-01-21 21:13:15 +03:00
|
|
|
&& let node = cx.tcx.hir_node(hir_id)
|
2023-07-31 23:53:53 +02:00
|
|
|
&& !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",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|