use std::collections::BTreeSet;
use super::*;
#[derive(Default)]
pub(super) struct Refs {
names: std::collections::BTreeSet<String>,
keep_all: bool,
}
impl Refs {
pub(super) fn add(&mut self, chunk_text: &str) {
match crate::referenced_idents(chunk_text) {
Some(names) => self.names.extend(names),
None => self.keep_all = true,
}
}
pub(super) fn add_source(&mut self, src: &str) {
match crate::referenced_idents_in_source(src) {
Some(names) => self.names.extend(names),
None => self.keep_all = true,
}
}
pub(super) fn is_empty(&self) -> bool {
self.names.is_empty() && !self.keep_all
}
fn keeps(&self, provided: &Option<std::collections::BTreeSet<String>>) -> bool {
match provided {
None => true,
Some(names) => self.keep_all || names.iter().any(|n| self.names.contains(n)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Rebase {
Keep,
Nested,
CrateRoot,
}
pub(super) fn rebase_import(chunk_text: &str, rebase: Rebase, local: &BTreeSet<String>) -> String {
if matches!(rebase, Rebase::Keep) {
return chunk_text.to_owned();
}
let Ok(syn::Item::Use(item)) = syn::parse_str::<syn::Item>(chunk_text) else {
return chunk_text.to_owned();
};
if !is_inherited(&item.vis) || item.leading_colon.is_some() {
return chunk_text.to_owned();
}
let mut edits = Vec::new();
collect_rebase_edits(&item.tree, rebase, &mut edits);
collect_local_anchor_edits(&item.tree, rebase, local, &mut edits);
apply_replacements(chunk_text, &edits)
}
fn collect_local_anchor_edits(
tree: &syn::UseTree,
rebase: Rebase,
local: &BTreeSet<String>,
edits: &mut Vec<(std::ops::Range<usize>, &'static str)>,
) {
let anchor = match rebase {
Rebase::Keep => return,
Rebase::Nested => "super::",
Rebase::CrateRoot => "crate::",
};
let ident = match tree {
syn::UseTree::Path(path) => &path.ident,
syn::UseTree::Rename(rename) => &rename.ident,
syn::UseTree::Group(group) => {
for item in &group.items {
collect_local_anchor_edits(item, rebase, local, edits);
}
return;
}
_ => return,
};
if local.contains(&ident.to_string()) {
let at = ident.span().byte_range().start;
edits.push((at..at, anchor));
}
}
pub(super) fn local_path_roots(exploded: &Exploded) -> BTreeSet<String> {
exploded
.manifest
.rows
.iter()
.filter(|row| matches!(row.kind.as_str(), "mod" | "enum"))
.map(|row| row.name.clone())
.collect()
}
pub(super) fn collect_rebase_edits(
tree: &syn::UseTree,
rebase: Rebase,
edits: &mut Vec<(std::ops::Range<usize>, &'static str)>,
) {
match tree {
syn::UseTree::Path(path) => collect_anchor_edit(&path.ident, rebase, edits),
syn::UseTree::Rename(rename) => collect_anchor_edit(&rename.ident, rebase, edits),
syn::UseTree::Group(group) => {
for item in &group.items {
collect_rebase_edits(item, rebase, edits);
}
}
_ => {}
}
}
pub(super) fn collect_anchor_edit(
ident: &syn::Ident,
rebase: Rebase,
edits: &mut Vec<(std::ops::Range<usize>, &'static str)>,
) {
let replacement = match (ident.to_string().as_str(), rebase) {
("super", Rebase::Nested) => "super::super",
("self", Rebase::Nested) => "super",
("self", Rebase::CrateRoot) => "crate",
_ => return,
};
edits.push((ident.span().byte_range(), replacement));
}
pub(super) fn apply_replacements(text: &str, edits: &[(std::ops::Range<usize>, &str)]) -> String {
let mut edits: Vec<(std::ops::Range<usize>, &str)> = edits.to_vec();
edits.sort_by_key(|(range, _)| range.start);
let mut out = String::with_capacity(text.len() + edits.len() * 8);
let mut prev = 0;
for (range, replacement) in &edits {
if range.start < prev {
continue;
}
out.push_str(&text[prev..range.start]);
out.push_str(replacement);
prev = range.end;
}
out.push_str(&text[prev..]);
out
}
pub(super) fn select_imports(
import_chunks: &[usize],
chunks: &[crate::Chunk],
refs: &Refs,
rebase: Rebase,
local: &BTreeSet<String>,
) -> String {
let mut out = String::new();
for &i in import_chunks {
let text = chunks[i].text.as_str();
if refs.keeps(&import_provided_names(text)) {
out.push_str(&rebase_import(text, rebase, local));
}
}
out
}
pub(super) fn import_provided_names(
chunk_text: &str,
) -> Option<std::collections::BTreeSet<String>> {
match syn::parse_str::<syn::Item>(chunk_text).ok()? {
syn::Item::Use(u) => {
let mut names = std::collections::BTreeSet::new();
let mut name_less = false;
collect_use_names(&u.tree, &mut names, &mut name_less);
if name_less { None } else { Some(names) }
}
syn::Item::ExternCrate(c) => {
let name = c
.rename
.map(|(_, id)| id.to_string())
.unwrap_or_else(|| c.ident.to_string());
Some(std::collections::BTreeSet::from([name]))
}
_ => None,
}
}
fn collect_use_names(
tree: &syn::UseTree,
names: &mut std::collections::BTreeSet<String>,
name_less: &mut bool,
) {
match tree {
syn::UseTree::Path(p) => collect_use_names(&p.tree, names, name_less),
syn::UseTree::Name(n) => {
names.insert(n.ident.to_string());
}
syn::UseTree::Rename(r) => {
if r.rename == "_" {
*name_less = true;
} else {
names.insert(r.rename.to_string());
}
}
syn::UseTree::Glob(_) => *name_less = true,
syn::UseTree::Group(g) => {
for item in &g.items {
collect_use_names(item, names, name_less);
}
}
}
}