use std::collections::{HashMap, HashSet};
use crate::cross_service::DetectedEndpoint;
use crate::dataflow::DetectedDataFlow;
use crate::error::Result;
use crate::schema::{Effect, SymbolKind};
#[derive(Debug, Clone)]
pub struct ParsedSymbol {
pub qname: String,
pub kind: SymbolKind,
pub start_line: u32,
pub start_col: u32,
pub end_line: u32,
pub end_col: u32,
pub body: String,
pub signature: Option<String>,
pub doc: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CallEdge {
pub caller_qname: String,
pub callee_qname: String,
}
#[derive(Debug, Clone, Default)]
pub struct WorkspaceSymbols {
pub qnames: HashSet<String>,
pub kinds: HashMap<String, SymbolKind>,
pub suffix_index: HashMap<String, Vec<String>>,
pub properties: HashMap<String, String>,
}
impl WorkspaceSymbols {
pub fn contains(&self, qname: &str) -> bool {
self.qnames.contains(qname)
}
pub fn build_suffix_index(&mut self) {
self.suffix_index.clear();
for qname in &self.qnames {
let parts: Vec<&str> = qname.split('.').collect();
for start in 1..parts.len() {
let suffix = parts[start..].join(".");
self.suffix_index
.entry(suffix)
.or_default()
.push(qname.clone());
}
}
}
pub fn find_by_suffix<'a>(&'a self, suffix: &str) -> Option<&'a str> {
if let Some(q) = self.qnames.get(suffix) {
return Some(q.as_str());
}
let matches = self.suffix_index.get(suffix)?;
if matches.len() == 1 {
Some(matches[0].as_str())
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UnresolvedCall {
pub file: String,
pub line: u32,
pub callee_text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DynamicDispatchHint {
pub file: String,
pub line: u32,
pub pattern: String,
pub snippet: String,
}
pub trait LanguageAdapter: Send + Sync {
fn language(&self) -> &str;
fn file_extensions(&self) -> &'static [&'static str] {
&[]
}
fn parse_symbols(&self, file: &str, source: &str) -> Result<Vec<ParsedSymbol>>;
fn infer_effects(&self, source: &str, symbol: &ParsedSymbol) -> Vec<Effect>;
fn scan_dynamic_dispatch(&self, _file: &str, _source: &str) -> Vec<DynamicDispatchHint> {
Vec::new()
}
fn report_unresolved_calls(
&self,
_file: &str,
_source: &str,
_symbols: &[ParsedSymbol],
_workspace: &WorkspaceSymbols,
) -> Vec<UnresolvedCall> {
Vec::new()
}
fn extract_call_edges(
&self,
_file: &str,
_source: &str,
_symbols: &[ParsedSymbol],
_workspace: &WorkspaceSymbols,
) -> Vec<CallEdge> {
Vec::new()
}
fn infer_service_endpoints(
&self,
_file: &str,
_source: &str,
_symbols: &[ParsedSymbol],
) -> Vec<DetectedEndpoint> {
Vec::new()
}
fn resolve_endpoint_prefixes(
&self,
_files: &[(String, String)],
_endpoints: &mut Vec<crate::cross_service::ServiceEndpoint>,
) {
}
fn extract_dataflow(
&self,
_file: &str,
_source: &str,
_symbols: &[ParsedSymbol],
_workspace: &WorkspaceSymbols,
) -> Vec<DetectedDataFlow> {
Vec::new()
}
fn extract_property_types(&self, _symbols: &[ParsedSymbol]) -> HashMap<String, String> {
HashMap::new()
}
}