use anyhow::{Context, Result};
use std::fs::File;
use std::io::Write;
use std::path::Path;
use super::schema::{generate_lsif_id, Edge, LsifGraph, PackageData, SymbolKind, Vertex};
use crate::graph::CodeGraph;
pub fn export_lsif(
graph: &mut CodeGraph,
output_path: &Path,
package_name: &str,
package_version: &str,
) -> Result<usize> {
let mut lsif = LsifGraph::new();
let mut counter = 0u32;
let mut exported_symbols = 0usize;
let package_id = generate_lsif_id("p", &mut counter);
let package = Vertex::Package {
id: package_id.clone(),
label: "package".to_string(),
data: PackageData {
name: package_name.to_string(),
version: package_version.to_string(),
manager: "cargo".to_string(),
},
};
lsif.add_vertex(package);
let files = graph.all_file_nodes()?;
let mut documents: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for (file_path, _file_node) in files {
let symbols = graph.symbols_in_file(&file_path)?;
let doc_id = if let Some(id) = documents.get(&file_path) {
id.clone()
} else {
let doc_id = generate_lsif_id("d", &mut counter);
let doc = Vertex::Document {
id: doc_id.clone(),
label: "document".to_string(),
uri: file_path.clone(),
language_id: detect_language_id(&file_path),
};
lsif.add_vertex(doc);
lsif.add_edge(Edge::Contains {
id: generate_lsif_id("e", &mut counter),
label: "contains".to_string(),
out_v: package_id.clone(),
in_vs: vec![doc_id.clone()],
});
documents.insert(file_path.clone(), doc_id.clone());
doc_id
};
for symbol in symbols {
let symbol_id = generate_lsif_id("s", &mut counter);
let symbol_kind = magellan_symbol_to_lsif_kind(&symbol.kind_normalized);
let _symbol_name = symbol.name.as_deref().unwrap_or("unknown");
let symbol_vertex = Vertex::Symbol {
id: symbol_id.clone(),
label: "symbol".to_string(),
kind: symbol_kind,
};
lsif.add_vertex(symbol_vertex);
let range_id = generate_lsif_id("r", &mut counter);
let range = [
symbol.start_line as u32,
symbol.start_col as u32,
symbol.end_line as u32,
symbol.end_col as u32,
];
let range_vertex = Vertex::Range {
id: range_id.clone(),
label: "range".to_string(),
range,
};
lsif.add_vertex(range_vertex);
lsif.add_edge(Edge::TextDocument {
id: generate_lsif_id("e", &mut counter),
label: "textDocument".to_string(),
out_v: doc_id.clone(),
in_v: range_id.clone(),
});
lsif.add_edge(Edge::Item {
id: generate_lsif_id("e", &mut counter),
label: "item".to_string(),
out_v: symbol_id.clone(),
in_vs: vec![range_id.clone()],
document: doc_id.clone(),
});
exported_symbols += 1;
}
}
let mut file = File::create(output_path)
.with_context(|| format!("Failed to create LSIF file: {:?}", output_path))?;
for vertex in &lsif.vertices {
let json = serde_json::to_string(vertex)?;
writeln!(file, "{}", json)?;
}
for edge in &lsif.edges {
let json = serde_json::to_string(edge)?;
writeln!(file, "{}", json)?;
}
Ok(exported_symbols)
}
fn magellan_symbol_to_lsif_kind(kind: &str) -> SymbolKind {
match kind {
"fn" | "function" => SymbolKind::Function,
"method" => SymbolKind::Method,
"struct" | "class" => SymbolKind::Struct,
"trait" | "interface" => SymbolKind::Interface,
"enum" => SymbolKind::Enum,
"mod" | "module" => SymbolKind::Module,
"type_alias" => SymbolKind::TypeParameter,
"union" => SymbolKind::Struct,
"namespace" => SymbolKind::Namespace,
_ => SymbolKind::Variable,
}
}
fn detect_language_id(file_path: &str) -> String {
let ext = std::path::Path::new(file_path)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
match ext {
"rs" => "rust".to_string(),
"py" => "python".to_string(),
"c" | "h" => "c".to_string(),
"cpp" | "hpp" | "cc" | "cxx" => "cpp".to_string(),
"java" => "java".to_string(),
"js" | "mjs" => "javascript".to_string(),
"ts" | "tsx" => "typescript".to_string(),
_ => "plaintext".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_export_lsif_basic() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test.db");
let output_path = temp_dir.path().join("test.lsif");
let mut graph = CodeGraph::open(&db_path).unwrap();
let test_file = temp_dir.path().join("test.rs");
std::fs::write(&test_file, "fn main() {}").unwrap();
let _ = graph.scan_directory(temp_dir.path(), None);
let result = export_lsif(&mut graph, &output_path, "test-crate", "0.1.0");
assert!(result.is_ok());
assert!(result.unwrap() >= 1);
assert!(output_path.exists());
let content = std::fs::read_to_string(&output_path).unwrap();
assert!(!content.is_empty());
assert!(content.contains("\"type\":\"package\""));
}
}