use clippy_utils::source::snippet_indent;
use rustc_ast::{Item, UseTree, UseTreeKind};
use rustc_lint::LateContext;
use rustc_span::Span;
use super::render::{
attr_snippets, is_self_leaf, real_segments, render_prefix, render_rooted, render_use_tree,
render_visibility, segment_names, simple_self_module, with_rename,
};
use super::{Fix, Pending};
const MESSAGE: &str = "this `use` imports a module through `self`";
const LABEL_BARE_PATH: &str = "import the module by its bare path";
const LABEL_SPLIT: &str = "split the module import into its own `use` statement";
const LABEL_EXPAND: &str = "import the module alongside the group's other items";
pub(super) fn check_use_item(
cx: &LateContext<'_>,
item: &Item,
tree: &UseTree,
violations: &mut Vec<Pending>,
) {
visit(cx, item, tree, true, violations);
}
fn visit(
cx: &LateContext<'_>,
item: &Item,
node: &UseTree,
at_root: bool,
violations: &mut Vec<Pending>,
) {
match &node.kind {
UseTreeKind::Simple(rename) => {
if let Some(module) = simple_self_module(node)
&& !module.is_empty()
{
let segments = real_segments(&node.prefix);
let module_path = render_rooted(&node.prefix, &segments[..segments.len() - 1]);
emit_replacement(
item,
node.span(),
LABEL_BARE_PATH,
with_rename(module_path, *rename),
Some(
"the trailing `self` is redundant here — it re-names the module the \
path already gives; import it directly",
),
violations,
);
}
}
UseTreeKind::Nested { items, .. } => {
if let Some(self_idx) = items.iter().position(|(child, _)| is_self_leaf(child)) {
rewrite_self_group(cx, item, node, items, self_idx, at_root, violations);
}
for (child, _) in items {
if !is_self_leaf(child) {
visit(cx, item, child, false, violations);
}
}
}
UseTreeKind::Glob(_) => {}
}
}
fn rewrite_self_group(
cx: &LateContext<'_>,
item: &Item,
node: &UseTree,
items: &[(UseTree, rustc_ast::NodeId)],
self_idx: usize,
at_root: bool,
violations: &mut Vec<Pending>,
) {
if segment_names(&node.prefix).is_empty() {
return;
}
let self_rename = match items[self_idx].0.kind {
UseTreeKind::Simple(rename) => rename,
_ => None,
};
let others: Vec<&UseTree> = items
.iter()
.enumerate()
.filter(|(index, _)| *index != self_idx)
.map(|(_, (child, _))| child)
.collect();
if others.iter().any(|tree| contains_self_form(tree)) {
return;
}
let base = render_prefix(&node.prefix);
let module = with_rename(base.clone(), self_rename);
if others.is_empty() {
emit_replacement(item, node.span(), LABEL_BARE_PATH, module, None, violations);
return;
}
let rest = render_rest(&base, &others);
if at_root {
let visibility = render_visibility(cx, item);
let indent = snippet_indent(cx, item.span).unwrap_or_default();
let attrs: String = attr_snippets(cx, item)
.iter()
.map(|attr| format!("{attr}\n{indent}"))
.collect();
let replacement = format!("{module};\n{indent}{attrs}{visibility}use {rest}");
emit_replacement(
item,
node.span(),
LABEL_SPLIT,
replacement,
None,
violations,
);
} else {
emit_replacement(
item,
node.span(),
LABEL_EXPAND,
format!("{module}, {rest}"),
None,
violations,
);
}
}
fn contains_self_form(tree: &UseTree) -> bool {
match &tree.kind {
UseTreeKind::Simple(_) => simple_self_module(tree).is_some_and(|module| !module.is_empty()),
UseTreeKind::Nested { items, .. } => {
let own_self_rewritten = !segment_names(&tree.prefix).is_empty()
&& items.iter().any(|(child, _)| is_self_leaf(child));
own_self_rewritten
|| items
.iter()
.any(|(child, _)| !is_self_leaf(child) && contains_self_form(child))
}
UseTreeKind::Glob(_) => false,
}
}
fn render_rest(base: &str, others: &[&UseTree]) -> String {
if let [single] = others {
format!("{base}::{}", render_use_tree(single))
} else {
let inner = others
.iter()
.map(|tree| render_use_tree(tree))
.collect::<Vec<_>>()
.join(", ");
format!("{base}::{{{inner}}}")
}
}
fn emit_replacement(
item: &Item,
span: Span,
label: &'static str,
replacement: String,
note: Option<&'static str>,
violations: &mut Vec<Pending>,
) {
violations.push(Pending {
anchor: item.span,
span,
message: MESSAGE,
fix: Fix::Replace {
label,
replacement,
note,
},
});
}