use std::collections::BTreeSet;
use crate::surface::{Field, Variant, enum_variants, struct_fields};
pub struct Universe {
sources: Vec<syn::File>,
}
enum Declaration {
Fields(Vec<Field>),
Variants(Vec<Variant>),
}
const PRIMITIVES: [&str; 17] = [
"bool", "char", "f32", "f64", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32",
"u64", "u128", "usize", "PathBuf",
];
const CONTAINERS: [&str; 8] = [
"Option", "Vec", "Box", "Arc", "Rc", "BTreeMap", "HashMap", "Result",
];
#[derive(Debug, Default, PartialEq, Eq)]
pub struct Findings {
pub strings: Vec<String>,
pub bytes: Vec<String>,
pub reached: BTreeSet<String>,
}
impl Universe {
#[must_use]
pub const fn new(sources: Vec<syn::File>) -> Self {
Self { sources }
}
fn resolve(&self, name: &str) -> Option<Declaration> {
for source in &self.sources {
let fields = struct_fields(source, name);
if !fields.is_empty() {
return Some(Declaration::Fields(fields));
}
let variants = enum_variants(source, name);
if !variants.is_empty() {
return Some(Declaration::Variants(variants));
}
}
None
}
fn walk(
&self,
path: &str,
spelling: &str,
findings: &mut Findings,
seen: &mut BTreeSet<String>,
) {
if spelling.contains("Vec<u8>")
|| spelling.contains("[u8]")
|| spelling.contains("RawBytes")
{
findings.bytes.push(path.to_owned());
}
for name in identifiers(spelling) {
if name == "String" || name == "str" {
findings.strings.push(path.to_owned());
continue;
}
if PRIMITIVES.contains(&name.as_str()) || CONTAINERS.contains(&name.as_str()) {
continue;
}
findings.reached.insert(name.clone());
if !seen.insert(name.clone()) {
continue;
}
match self.resolve(&name) {
Some(Declaration::Fields(fields)) => {
for field in fields {
self.walk(
&format!("{path}/{name}.{}", field.name),
&field.ty,
findings,
seen,
);
}
}
Some(Declaration::Variants(variants)) => {
for variant in variants {
for field in variant.fields {
self.walk(
&format!("{path}/{name}::{}.{}", variant.name, field.name),
&field.ty,
findings,
seen,
);
}
}
}
None => {}
}
}
}
#[must_use]
pub fn closure(&self, roots: &[(String, String)]) -> Findings {
let mut findings = Findings::default();
let mut seen = BTreeSet::new();
for (path, spelling) in roots {
self.walk(path, spelling, &mut findings, &mut seen);
}
findings
}
}
fn identifiers(spelling: &str) -> Vec<String> {
let mut names = Vec::new();
let mut current = String::new();
for character in spelling.chars() {
if character.is_alphanumeric() || character == '_' {
current.push(character);
} else if !current.is_empty() {
names.push(std::mem::take(&mut current));
}
}
if !current.is_empty() {
names.push(current);
}
names
}