use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub mod query;
pub mod resolve;
pub const SYMBOL_GRAPH_SCHEMA_VERSION: &str = "loctree.symbol_graph.v1";
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SymbolId(pub String);
impl SymbolId {
pub fn new(descriptor: impl Into<String>) -> Self {
Self(descriptor.into())
}
pub fn from_parts(file: &str, kind: &str, name: &str, range_hash: u64) -> Self {
Self(format!("{file}::{kind}::{name}::{range_hash:016x}"))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl std::fmt::Display for SymbolId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for SymbolId {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for SymbolId {
fn from(value: &str) -> Self {
Self(value.to_string())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LanguageId {
C,
Cpp,
ObjC,
ObjCpp,
Swift,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SymbolKind {
Type,
Class,
Struct,
Protocol,
Enum,
Func,
Method,
Property,
Field,
Var,
Macro,
Typedef,
Selector,
Module,
Namespace,
Other(String),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SymbolProvenance {
TreeSitter,
IndexStore,
ScipClang,
ScipSwift,
Clangd,
Heuristic,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Confidence {
Heuristic,
Precise,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SymbolVisibility {
Open,
Public,
Package,
Internal,
FilePrivate,
Private,
Unknown,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OccurrenceRole {
Definition,
Declaration,
Reference,
Call,
Import,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SymbolEdgeKind {
Defines,
Declares,
References,
Calls,
Overrides,
Conforms,
Implements,
Inherits,
Includes,
ImportsModule,
Instantiates,
MacroExpands,
SelectorMessage,
NotificationEmit,
NotificationObserve,
IBOutletBinding,
IBActionBinding,
Bridges,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct TextRange {
pub start_byte: usize,
pub end_byte: usize,
pub start_line: usize,
pub start_col: usize,
pub end_line: usize,
pub end_col: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SymbolNode {
pub id: SymbolId,
pub language: LanguageId,
pub kind: SymbolKind,
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub qualified_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub module: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub usr: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub range: Option<TextRange>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub visibility: Option<SymbolVisibility>,
pub provenance: SymbolProvenance,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SymbolOccurrence {
pub symbol_id: SymbolId,
pub file: PathBuf,
pub range: TextRange,
pub role: OccurrenceRole,
pub confidence: Confidence,
pub engine: SymbolProvenance,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SymbolEdge {
pub from: SymbolId,
pub to: SymbolId,
pub kind: SymbolEdgeKind,
pub provenance: SymbolProvenance,
pub confidence: Confidence,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SymbolEngineRun {
pub engine: SymbolProvenance,
#[serde(default)]
pub symbol_count: usize,
#[serde(default)]
pub occurrence_count: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_version: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileSymbolSummary {
pub file: PathBuf,
#[serde(default)]
pub defined: Vec<SymbolId>,
#[serde(default)]
pub referenced: Vec<SymbolId>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SymbolGraph {
#[serde(default)]
pub schema_version: String,
#[serde(default)]
pub engines: Vec<SymbolEngineRun>,
#[serde(default)]
pub symbols: Vec<SymbolNode>,
#[serde(default)]
pub occurrences: Vec<SymbolOccurrence>,
#[serde(default)]
pub edges: Vec<SymbolEdge>,
#[serde(default)]
pub file_projection: Vec<FileSymbolSummary>,
}
impl Default for SymbolGraph {
fn default() -> Self {
Self {
schema_version: SYMBOL_GRAPH_SCHEMA_VERSION.to_string(),
engines: Vec::new(),
symbols: Vec::new(),
occurrences: Vec::new(),
edges: Vec::new(),
file_projection: Vec::new(),
}
}
}
impl SymbolGraph {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.symbols.is_empty() && self.occurrences.is_empty() && self.edges.is_empty()
}
}