use crate::core::{Declaration, DeclarationKind, ParseResult};
use crate::parse_file;
use crate::surface::entry::{ReExportHop, SurfaceEntry};
use crate::surface::entry_point::EntryPoint;
use crate::surface::imports::{self, RustImports, UseItem, UseSegmentKind};
use crate::surface::module_graph::resolve_mod_file;
use crate::surface::options::{SurfaceError, SurfaceOptions};
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
pub fn resolve(
entry: &EntryPoint,
opts: &SurfaceOptions,
) -> Result<Vec<SurfaceEntry>, SurfaceError> {
let (root_file, crate_name) = match entry {
EntryPoint::RustCrate {
root_file,
crate_name,
..
} => (root_file.clone(), crate_name.clone()),
_ => {
return Err(SurfaceError::NoEntryPoint {
path: PathBuf::from("."),
hint: "rust::resolve called with non-Rust entry point".into(),
});
}
};
let mut graph = ModuleGraph::new();
graph.load_file(&root_file, vec![crate_name.clone()])?;
let mut walker = SurfaceWalker::new(crate_name.clone(), graph, opts.max_depth);
walker.walk();
Ok(walker.entries)
}
struct ModuleData {
file: PathBuf,
parse: ParseResult,
imports: RustImports,
}
struct ModuleGraph {
modules: HashMap<String, ModuleData>,
parents: HashMap<String, String>,
}
impl ModuleGraph {
fn new() -> Self {
Self {
modules: HashMap::new(),
parents: HashMap::new(),
}
}
fn key(segments: &[String]) -> String {
segments.join("::")
}
fn load_file(&mut self, file: &Path, segments: Vec<String>) -> Result<(), SurfaceError> {
let key = Self::key(&segments);
if self.modules.contains_key(&key) {
return Ok(());
}
let parse = parse_file(file).ok_or_else(|| SurfaceError::Parse {
path: file.to_path_buf(),
message: "could not parse file".into(),
})?;
let src = std::str::from_utf8(&parse.source).unwrap_or("").to_string();
let imports = imports::extract_rust_imports(&src);
if segments.len() > 1 {
let parent = segments[..segments.len() - 1].join("::");
self.parents.insert(key.clone(), parent);
}
let mod_refs: Vec<_> = imports
.mods
.iter()
.filter(|m| m.is_external_file)
.cloned()
.collect();
self.modules.insert(
key,
ModuleData {
file: file.to_path_buf(),
parse,
imports,
},
);
for m in mod_refs {
if let Some(child_file) = resolve_mod_file(file, &m) {
let mut child_segments = segments.clone();
child_segments.push(m.name.clone());
let _ = self.load_file(&child_file, child_segments);
}
}
Ok(())
}
}
struct SurfaceWalker {
crate_name: String,
graph: ModuleGraph,
max_depth: usize,
visited: HashSet<(String, String)>,
entries: Vec<SurfaceEntry>,
seen_qualified: HashSet<String>,
}
impl SurfaceWalker {
fn new(crate_name: String, graph: ModuleGraph, max_depth: usize) -> Self {
Self {
crate_name,
graph,
max_depth,
visited: HashSet::new(),
entries: Vec::new(),
seen_qualified: HashSet::new(),
}
}
fn walk(&mut self) {
let mut frontier: Vec<Vec<String>> = vec![vec![self.crate_name.clone()]];
let mut seen_modules: HashSet<String> = HashSet::new();
while let Some(segments) = frontier.pop() {
let key = ModuleGraph::key(&segments);
if !seen_modules.insert(key.clone()) {
continue;
}
let module_owned = match self.graph.modules.get(&key) {
Some(m) => (
m.file.clone(),
m.parse.declarations.clone(),
m.imports.uses.clone(),
m.imports.mods.clone(),
),
None => continue,
};
let (mod_file, decls, uses, mods) = module_owned;
for d in &decls {
if matches!(d.kind, DeclarationKind::Namespace) {
continue;
}
if let Some(impl_target) = _impl_target(d) {
let mut nested = segments.clone();
nested.push(impl_target.to_string());
for child in &d.children {
if _is_public(child) {
self._emit_decl(&nested, child, &mod_file, vec![], false);
}
}
continue;
}
if _is_public(d) {
self._emit_decl(&segments, d, &mod_file, vec![], false);
if _is_type_with_methods(d) {
let mut nested = segments.clone();
nested.push(d.name.clone());
for child in &d.children {
if _is_public(child) && _is_method_like(child) {
self._emit_decl(&nested, child, &mod_file, vec![], false);
}
}
}
}
}
for m in &mods {
if !_vis_is_public(&m.visibility) {
continue;
}
let mut child = segments.clone();
child.push(m.name.clone());
frontier.push(child);
}
for u in &uses {
if !_vis_is_public(&u.visibility) {
continue;
}
let hop = ReExportHop {
file: mod_file.clone(),
line: u.line,
module_path: key.clone(),
statement: u.statement.clone(),
};
self._resolve_use(&segments, u, vec![hop], 0);
}
}
}
fn _emit_decl(
&mut self,
module_segments: &[String],
decl: &Declaration,
source_file: &Path,
chain: Vec<ReExportHop>,
via_glob: bool,
) {
if decl.name.is_empty() {
return;
}
let qpath = format!("{}::{}", module_segments.join("::"), decl.name);
if !self.seen_qualified.insert(qpath.clone()) {
return;
}
self.entries.push(SurfaceEntry {
qualified_path: qpath,
kind: decl.kind,
signature: decl.signature.clone(),
source_path: source_file.to_path_buf(),
source_line: decl.start_line,
source_name: decl.name.clone(),
re_export_chain: chain,
via_glob,
docs: decl.docs.clone(),
});
}
fn _emit_renamed(
&mut self,
target_module: &[String],
rename_to: &str,
source_decl: &Declaration,
source_file: &Path,
chain: Vec<ReExportHop>,
via_glob: bool,
) {
let qpath = format!("{}::{}", target_module.join("::"), rename_to);
if !self.seen_qualified.insert(qpath.clone()) {
return;
}
self.entries.push(SurfaceEntry {
qualified_path: qpath,
kind: source_decl.kind,
signature: source_decl.signature.clone(),
source_path: source_file.to_path_buf(),
source_line: source_decl.start_line,
source_name: source_decl.name.clone(),
re_export_chain: chain,
via_glob,
docs: source_decl.docs.clone(),
});
}
fn _resolve_use(
&mut self,
from_segments: &[String],
u: &UseItem,
chain: Vec<ReExportHop>,
depth: usize,
) {
if depth > self.max_depth {
return;
}
let path_segments: Vec<String> = u.path.split("::").map(|s| s.to_string()).collect();
let resolved = match _resolve_path(&self.crate_name, from_segments, &path_segments) {
Some(r) => r,
None => return, };
match u.kind {
UseSegmentKind::Item => {
if resolved.is_empty() {
return;
}
let item = resolved.last().unwrap().clone();
let target_module = resolved[..resolved.len() - 1].to_vec();
let cycle_key = (target_module.join("::"), item.clone());
if !self.visited.insert(cycle_key) {
return;
}
let alias = u.alias.clone().unwrap_or_else(|| item.clone());
self._republish(from_segments, &target_module, &item, &alias, chain, false, depth);
}
UseSegmentKind::Glob => {
let target_module = resolved.clone();
let key = ModuleGraph::key(&target_module);
let target_data = match self.graph.modules.get(&key) {
Some(m) => (m.file.clone(), m.parse.declarations.clone(), m.imports.uses.clone()),
None => return,
};
let (target_file, target_decls, target_uses) = target_data;
for d in &target_decls {
if !_is_public(d) || matches!(d.kind, DeclarationKind::Namespace) {
continue;
}
let cycle_key = (key.clone(), d.name.clone());
if !self.visited.insert(cycle_key) {
continue;
}
self._emit_renamed(
from_segments,
&d.name,
d,
&target_file,
chain.clone(),
true,
);
}
for tu in target_uses {
if !_vis_is_public(&tu.visibility) {
continue;
}
let mut next_chain = chain.clone();
next_chain.push(ReExportHop {
file: target_file.clone(),
line: tu.line,
module_path: key.clone(),
statement: tu.statement.clone(),
});
self._republish_via_use(from_segments, &target_module, &tu, next_chain, depth + 1);
}
}
}
}
fn _republish(
&mut self,
from_segments: &[String],
target_module: &[String],
item: &str,
alias: &str,
chain: Vec<ReExportHop>,
via_glob: bool,
depth: usize,
) {
let key = ModuleGraph::key(target_module);
let target_data = match self.graph.modules.get(&key) {
Some(m) => (m.file.clone(), m.parse.declarations.clone(), m.imports.uses.clone()),
None => return,
};
let (target_file, target_decls, target_uses) = target_data;
for d in &target_decls {
if d.name == item && _is_public(d) {
self._emit_renamed(from_segments, alias, d, &target_file, chain, via_glob);
return;
}
}
for tu in target_uses {
if !_vis_is_public(&tu.visibility) {
continue;
}
let local_name = tu
.alias
.clone()
.unwrap_or_else(|| _last_segment(&tu.path).to_string());
let matches_item = match tu.kind {
UseSegmentKind::Item => local_name == item,
UseSegmentKind::Glob => true,
};
if !matches_item {
continue;
}
let mut next_chain = chain.clone();
next_chain.push(ReExportHop {
file: target_file.clone(),
line: tu.line,
module_path: key.clone(),
statement: tu.statement.clone(),
});
let nested_segments: Vec<String> = tu.path.split("::").map(|s| s.to_string()).collect();
let nested_resolved =
match _resolve_path(&self.crate_name, target_module, &nested_segments) {
Some(r) => r,
None => continue,
};
match tu.kind {
UseSegmentKind::Item => {
if nested_resolved.is_empty() {
continue;
}
let nested_item = nested_resolved.last().unwrap().clone();
let nested_module = nested_resolved[..nested_resolved.len() - 1].to_vec();
self._republish(
from_segments,
&nested_module,
&nested_item,
alias,
next_chain,
via_glob,
depth + 1,
);
}
UseSegmentKind::Glob => {
self._republish(
&from_segments.to_vec(),
&nested_resolved,
item,
alias,
next_chain,
true,
depth + 1,
);
}
}
}
}
fn _republish_via_use(
&mut self,
from_segments: &[String],
upstream_module: &[String],
u: &UseItem,
chain: Vec<ReExportHop>,
depth: usize,
) {
if depth > self.max_depth {
return;
}
let path_segments: Vec<String> = u.path.split("::").map(|s| s.to_string()).collect();
let resolved =
match _resolve_path(&self.crate_name, upstream_module, &path_segments) {
Some(r) => r,
None => return,
};
match u.kind {
UseSegmentKind::Item => {
if resolved.is_empty() {
return;
}
let item = resolved.last().unwrap().clone();
let target_module = resolved[..resolved.len() - 1].to_vec();
let alias = u.alias.clone().unwrap_or_else(|| item.clone());
self._republish(
from_segments,
&target_module,
&item,
&alias,
chain,
true,
depth,
);
}
UseSegmentKind::Glob => {
let target_module = resolved.clone();
let key = ModuleGraph::key(&target_module);
let snap = match self.graph.modules.get(&key) {
Some(m) => (m.file.clone(), m.parse.declarations.clone()),
None => return,
};
for d in snap.1 {
if _is_public(&d) && !matches!(d.kind, DeclarationKind::Namespace) {
let cycle_key = (key.clone(), d.name.clone());
if !self.visited.insert(cycle_key) {
continue;
}
self._emit_renamed(from_segments, &d.name, &d, &snap.0, chain.clone(), true);
}
}
}
}
}
}
fn _resolve_path(
crate_name: &str,
from: &[String],
path: &[String],
) -> Option<Vec<String>> {
if path.is_empty() {
return None;
}
let mut out: Vec<String> = Vec::new();
let mut iter = path.iter().peekable();
let first = iter.peek().map(|s| s.as_str()).unwrap_or("");
match first {
"crate" => {
iter.next();
out.push(crate_name.to_string());
}
"self" => {
iter.next();
out.extend(from.iter().cloned());
}
"super" => {
out.extend(from.iter().cloned());
while iter.peek().map(|s| s.as_str()) == Some("super") {
iter.next();
if out.pop().is_none() {
return None;
}
}
}
other => {
let mut child = from.to_vec();
child.push(other.to_string());
if child[0] == crate_name {
out.extend(child);
iter.next();
} else {
out.push(crate_name.to_string());
out.extend(from.iter().skip(1).cloned());
out.push(other.to_string());
iter.next();
}
}
}
while let Some(seg) = iter.next() {
out.push(seg.clone());
}
Some(out)
}
fn _is_public(d: &Declaration) -> bool {
_vis_is_public(&d.visibility)
}
fn _impl_target(d: &Declaration) -> Option<&str> {
if !matches!(d.kind, DeclarationKind::Class) {
return None;
}
let name = d.name.strip_prefix("impl_")?;
if !d.bases.is_empty() {
return None;
}
Some(name)
}
fn _is_type_with_methods(d: &Declaration) -> bool {
matches!(
d.kind,
DeclarationKind::Struct
| DeclarationKind::Enum
| DeclarationKind::Interface
| DeclarationKind::Class
| DeclarationKind::Record
)
}
fn _is_method_like(d: &Declaration) -> bool {
matches!(
d.kind,
DeclarationKind::Method
| DeclarationKind::Function
| DeclarationKind::Constructor
)
}
fn _vis_is_public(v: &str) -> bool {
let v = v.trim();
v == "pub"
|| v.starts_with("pub ")
|| v.starts_with("pub(")
}
fn _last_segment(path: &str) -> &str {
path.rsplit("::").next().unwrap_or(path)
}