use rustc_ast::{Item, ItemKind, UseTree, UseTreeKind};
use rustc_errors::Applicability;
use rustc_lint::{LateContext, LintContext};
use rustc_span::{Symbol, kw};
use super::render::{
attr_snippets, has_path_root, is_self_leaf, real_segments, render_prefix, render_segments,
render_use_tree, render_visibility, segment_names, with_rename,
};
use super::{Fix, Pending};
pub(super) fn scan<'ast>(
cx: &LateContext<'_>,
entries: impl Iterator<Item = Option<&'ast Item>>,
violations: &mut Vec<Pending>,
) {
let mut previous: Option<&Item> = None;
for entry in entries {
match entry {
Some(item) if matches!(item.kind, ItemKind::Use(_)) && !item.span.from_expansion() => {
if let Some(first) = previous
&& try_fold(cx, first, item, violations)
{
previous = None;
} else {
previous = Some(item);
}
}
_ => previous = None,
}
}
}
fn try_fold(
cx: &LateContext<'_>,
first: &Item,
second: &Item,
violations: &mut Vec<Pending>,
) -> bool {
if render_visibility(cx, first) != render_visibility(cx, second) {
return false;
}
if attr_snippets(cx, first) != attr_snippets(cx, second) {
return false;
}
let (ItemKind::Use(first_tree), ItemKind::Use(second_tree)) = (&first.kind, &second.kind)
else {
return false;
};
if has_path_root(&first_tree.prefix) != has_path_root(&second_tree.prefix) {
return false;
}
if let Some((module, bare)) = module_import(first_tree)
&& let Some(tail) = item_tail_under(second_tree, &module)
{
emit_fold(cx, first, second, first_tree, bare, &tail, violations);
return true;
}
if let Some((module, bare)) = module_import(second_tree)
&& let Some(tail) = item_tail_under(first_tree, &module)
{
emit_fold(cx, first, second, second_tree, bare, &tail, violations);
return true;
}
false
}
fn module_import(tree: &UseTree) -> Option<(Vec<Symbol>, bool)> {
match &tree.kind {
UseTreeKind::Simple(None) => {
let names = segment_names(&tree.prefix);
if names.is_empty() || names.last() == Some(&kw::SelfLower) {
return None;
}
Some((names, true))
}
UseTreeKind::Nested { items, .. } => {
let [(only, _)] = items.as_slice() else {
return None;
};
if !is_self_leaf(only) || matches!(only.kind, UseTreeKind::Simple(Some(_))) {
return None;
}
let names = segment_names(&tree.prefix);
if names.is_empty() {
return None;
}
Some((names, false))
}
_ => None,
}
}
fn item_tail_under(tree: &UseTree, module: &[Symbol]) -> Option<String> {
let names = segment_names(&tree.prefix);
if names.len() < module.len() || &names[..module.len()] != module {
return None;
}
let rest_names = &names[module.len()..];
if rest_names.contains(&kw::SelfLower) {
return None;
}
let rest_segments = &real_segments(&tree.prefix)[module.len()..];
match &tree.kind {
UseTreeKind::Simple(rename) => {
if rest_segments.is_empty() {
return None;
}
Some(with_rename(render_segments(rest_segments), *rename))
}
UseTreeKind::Nested { items, .. } => {
if items.iter().any(|(child, _)| is_self_leaf(child)) {
return None;
}
let inner = items
.iter()
.map(|(child, _)| render_use_tree(child))
.collect::<Vec<_>>()
.join(", ");
if rest_segments.is_empty() {
Some(inner)
} else {
Some(format!("{}::{{{inner}}}", render_segments(rest_segments)))
}
}
UseTreeKind::Glob(_) => None,
}
}
fn emit_fold(
cx: &LateContext<'_>,
first: &Item,
second: &Item,
module_tree: &UseTree,
bare: bool,
tail: &str,
violations: &mut Vec<Pending>,
) {
let ItemKind::Use(first_tree) = &first.kind else {
return;
};
let module = render_prefix(&module_tree.prefix);
let folded = format!("{module}::{{self, {tail}}}");
let delete = first.span.shrink_to_hi().to(second.span);
let source_map = cx.sess().source_map();
let gap = source_map
.span_to_snippet(first.span.between(second.span))
.unwrap_or_default();
let deleted = source_map.span_to_snippet(delete).unwrap_or_default();
let replaced = source_map
.span_to_snippet(first_tree.span())
.unwrap_or_default();
let has_comment = [&deleted, &replaced]
.iter()
.any(|text| text.contains("//") || text.contains("/*"));
let applicability = if bare || !gap.trim().is_empty() || has_comment {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
};
violations.push(Pending {
anchor: first.span,
span: first.span,
message: "adjacent module and item imports can be combined through `self`",
fix: Fix::Multipart {
label: "combine into a single `use` with `self`",
parts: vec![(first_tree.span(), folded), (delete, String::new())],
applicability,
},
});
}