use std::path::PathBuf;
use crate::language::LangId;
use crate::model::{FileId, SnapshotId, SourceRange, SymbolId, SymbolKind, Visibility};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum NodeData {
File(FileNode),
Symbol(SymbolNode),
External(ExternalNode),
}
impl NodeData {
pub fn kind_str(&self) -> &'static str {
match self {
NodeData::File(_) => "file",
NodeData::Symbol(_) => "symbol",
NodeData::External(_) => "external",
}
}
pub fn as_file(&self) -> Option<&FileNode> {
if let NodeData::File(f) = self {
Some(f)
} else {
None
}
}
pub fn as_symbol(&self) -> Option<&SymbolNode> {
if let NodeData::Symbol(s) = self {
Some(s)
} else {
None
}
}
pub fn as_external(&self) -> Option<&ExternalNode> {
if let NodeData::External(e) = self {
Some(e)
} else {
None
}
}
pub fn file_path(&self) -> Option<&PathBuf> {
self.as_file().map(|f| &f.path)
}
pub fn symbol_name(&self) -> Option<&str> {
self.as_symbol().map(|s| s.name.as_str())
}
}
#[derive(Debug, Clone)]
pub struct FileNode {
pub id: FileId,
pub path: PathBuf,
pub language: LangId,
pub snapshot_id: SnapshotId,
}
#[derive(Debug, Clone)]
pub struct ExternalNode {
pub raw_path: String,
pub language: LangId,
pub classification: Option<ExternalClassification>,
}
#[derive(Debug, Clone, serde::Serialize)]
#[non_exhaustive]
pub enum ExternalClassification {
Classified {
package_name: String,
version: Option<String>,
language: LangId,
source: DependencySource,
},
Unresolved { raw_path: String, reason: String },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
#[non_exhaustive]
pub enum DependencySource {
Lockfile,
Manifest,
}
#[derive(Debug, Clone)]
pub struct SymbolNode {
pub id: SymbolId,
pub name: String,
pub kind: SymbolKind,
pub file_id: FileId,
pub visibility: Option<Visibility>,
pub source_range: SourceRange,
}
impl FileNode {
pub fn new(id: FileId, path: PathBuf, language: LangId, snapshot_id: SnapshotId) -> Self {
Self {
id,
path,
language,
snapshot_id,
}
}
pub fn file_name(&self) -> Option<&str> {
self.path.file_name().and_then(|n| n.to_str())
}
pub fn extension(&self) -> Option<&str> {
self.path.extension().and_then(|e| e.to_str())
}
}
impl SymbolNode {
pub fn from_symbol(symbol: &crate::model::Symbol, file_id: FileId) -> Self {
Self {
id: symbol.id,
name: symbol.name.clone(),
kind: symbol.kind,
file_id,
visibility: symbol.visibility,
source_range: symbol.source_range.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{LineColumn, SourceRange};
fn test_path() -> PathBuf {
PathBuf::from("src/main.rs")
}
fn test_source_range() -> SourceRange {
SourceRange {
byte_start: 0,
byte_end: 10,
start: LineColumn { line: 0, column: 0 },
end: LineColumn {
line: 0,
column: 10,
},
}
}
#[test]
fn file_node_creation() {
let file_id = FileId::from(0);
let snapshot_id = SnapshotId::from(1);
let node = FileNode::new(file_id, test_path(), LangId::Rust, snapshot_id);
assert_eq!(node.id, file_id);
assert_eq!(node.path, test_path());
assert_eq!(node.language, LangId::Rust);
assert_eq!(node.snapshot_id, snapshot_id);
}
#[test]
fn file_node_file_name() {
let node = FileNode::new(
FileId::from(0),
PathBuf::from("src/main.rs"),
LangId::Rust,
SnapshotId::from(0),
);
assert_eq!(node.file_name(), Some("main.rs"));
}
#[test]
fn file_node_extension() {
let node = FileNode::new(
FileId::from(0),
PathBuf::from("test.py"),
LangId::Python,
SnapshotId::from(0),
);
assert_eq!(node.extension(), Some("py"));
}
#[test]
fn symbol_node_creation() {
let symbol = crate::model::Symbol {
id: SymbolId::from(42),
name: "test_function".to_string(),
kind: SymbolKind::Function,
language: LangId::Rust,
file_path: test_path(),
source_range: test_source_range(),
visibility: Some(Visibility::Public),
signature: None,
docstring: None,
is_async: false,
};
let file_id = FileId::from(7);
let node = SymbolNode::from_symbol(&symbol, file_id);
assert_eq!(node.id, SymbolId::from(42));
assert_eq!(node.name, "test_function");
assert_eq!(node.kind, SymbolKind::Function);
assert_eq!(node.file_id, file_id);
assert_eq!(node.visibility, Some(Visibility::Public));
}
#[test]
fn node_data_file_variant() {
let file_node = FileNode::new(
FileId::from(0),
test_path(),
LangId::Rust,
SnapshotId::from(0),
);
let node_data = NodeData::File(file_node);
assert_eq!(node_data.kind_str(), "file");
assert!(node_data.as_file().is_some());
assert!(node_data.as_symbol().is_none());
assert_eq!(node_data.file_path(), Some(&test_path()));
assert_eq!(node_data.symbol_name(), None);
}
#[test]
fn node_data_symbol_variant() {
let symbol_node = SymbolNode {
id: SymbolId::from(1),
name: "my_func".to_string(),
kind: SymbolKind::Function,
file_id: FileId::from(0),
visibility: None,
source_range: test_source_range(),
};
let node_data = NodeData::Symbol(symbol_node);
assert_eq!(node_data.kind_str(), "symbol");
assert!(node_data.as_symbol().is_some());
assert!(node_data.as_file().is_none());
assert_eq!(node_data.file_path(), None);
assert_eq!(node_data.symbol_name(), Some("my_func"));
}
}