use serde::{Deserialize, Serialize};
use std::fmt;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
pub enum Language {
C,
Cpp,
CSharp,
Css,
JavaScript,
Python,
TypeScript,
Rust,
Go,
Java,
Ruby,
Php,
Swift,
Kotlin,
Scala,
Sql,
Dart,
Lua,
Perl,
Shell,
Groovy,
Elixir,
R,
Haskell,
Html,
Svelte,
Vue,
Zig,
Terraform,
Puppet,
Pulumi,
Http,
Plsql,
Apex,
Abap,
ServiceNow,
Json,
}
impl fmt::Display for Language {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Language::C => write!(f, "c"),
Language::Cpp => write!(f, "cpp"),
Language::CSharp => write!(f, "csharp"),
Language::Css => write!(f, "css"),
Language::JavaScript => write!(f, "js"),
Language::Python => write!(f, "py"),
Language::TypeScript => write!(f, "ts"),
Language::Rust => write!(f, "rust"),
Language::Go => write!(f, "go"),
Language::Java => write!(f, "java"),
Language::Ruby => write!(f, "ruby"),
Language::Php => write!(f, "php"),
Language::Swift => write!(f, "swift"),
Language::Kotlin => write!(f, "kotlin"),
Language::Scala => write!(f, "scala"),
Language::Sql => write!(f, "sql"),
Language::Dart => write!(f, "dart"),
Language::Lua => write!(f, "lua"),
Language::Perl => write!(f, "perl"),
Language::Shell => write!(f, "shell"),
Language::Groovy => write!(f, "groovy"),
Language::Elixir => write!(f, "elixir"),
Language::R => write!(f, "r"),
Language::Haskell => write!(f, "haskell"),
Language::Html => write!(f, "html"),
Language::Svelte => write!(f, "svelte"),
Language::Vue => write!(f, "vue"),
Language::Zig => write!(f, "zig"),
Language::Terraform => write!(f, "terraform"),
Language::Puppet => write!(f, "puppet"),
Language::Pulumi => write!(f, "pulumi"),
Language::Http => write!(f, "http"),
Language::Plsql => write!(f, "plsql"),
Language::Apex => write!(f, "apex"),
Language::Abap => write!(f, "abap"),
Language::ServiceNow => write!(f, "servicenow"),
Language::Json => write!(f, "json"),
}
}
}
impl Language {
#[must_use]
pub fn from_id(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"c" => Some(Self::C),
"cpp" | "c++" => Some(Self::Cpp),
"csharp" | "c#" | "cs" => Some(Self::CSharp),
"css" => Some(Self::Css),
"javascript" | "js" => Some(Self::JavaScript),
"python" | "py" => Some(Self::Python),
"typescript" | "ts" => Some(Self::TypeScript),
"rust" | "rs" => Some(Self::Rust),
"go" | "golang" => Some(Self::Go),
"java" => Some(Self::Java),
"ruby" | "rb" => Some(Self::Ruby),
"php" => Some(Self::Php),
"swift" => Some(Self::Swift),
"kotlin" | "kt" => Some(Self::Kotlin),
"scala" => Some(Self::Scala),
"sql" => Some(Self::Sql),
"dart" => Some(Self::Dart),
"lua" => Some(Self::Lua),
"perl" | "pl" => Some(Self::Perl),
"shell" | "bash" | "sh" => Some(Self::Shell),
"groovy" => Some(Self::Groovy),
"elixir" | "ex" | "exs" => Some(Self::Elixir),
"r" => Some(Self::R),
"haskell" | "hs" => Some(Self::Haskell),
"html" => Some(Self::Html),
"svelte" => Some(Self::Svelte),
"vue" => Some(Self::Vue),
"zig" => Some(Self::Zig),
"terraform" | "hcl" => Some(Self::Terraform),
"puppet" => Some(Self::Puppet),
"pulumi" => Some(Self::Pulumi),
"http" => Some(Self::Http),
"plsql" => Some(Self::Plsql),
"apex" | "salesforce" => Some(Self::Apex),
"abap" => Some(Self::Abap),
"servicenow" => Some(Self::ServiceNow),
"json" => Some(Self::Json),
_ => None,
}
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct NodeId {
pub language: Language,
pub file: Arc<str>,
pub qualified_name: Arc<str>,
}
impl NodeId {
pub fn new(language: Language, file: impl AsRef<str>, qualified_name: impl AsRef<str>) -> Self {
Self {
language,
file: Arc::from(file.as_ref()),
qualified_name: Arc::from(qualified_name.as_ref()),
}
}
#[must_use]
pub fn symbol_name(&self) -> &str {
if let Some(name) = self.qualified_name.rsplit("::").next()
&& name != self.qualified_name.as_ref()
{
return name;
}
if let Some(name) = self.qualified_name.rsplit('.').next() {
return name;
}
&self.qualified_name
}
}
impl fmt::Display for NodeId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}:{}", self.language, self.file, self.qualified_name)
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
pub struct Span {
pub start: Position,
pub end: Position,
}
impl Span {
#[must_use]
pub fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
#[must_use]
pub fn from_bytes(start: usize, end: usize) -> Self {
Self {
start: Position {
line: 0,
column: start,
},
end: Position {
line: 0,
column: end,
},
}
}
}
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
pub struct Position {
pub line: usize,
pub column: usize,
}
impl Position {
#[must_use]
pub fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NodeKind {
Function {
params: Vec<Param>,
return_type: Option<Type>,
is_async: bool,
},
Class {
bases: Vec<NodeId>,
interfaces: Vec<NodeId>,
},
Module {
exports: Vec<NodeId>,
},
Variable {
var_type: Option<Type>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct Param {
pub name: String,
pub param_type: Option<Type>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Type {
pub name: String,
}
#[derive(Debug, Clone, Default)]
pub struct NodeMetadata {
pub visibility: Option<String>,
pub doc_comment: Option<String>,
pub attributes: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct CodeNode {
pub id: NodeId,
pub kind: NodeKind,
pub span: Span,
pub metadata: NodeMetadata,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_node_id_creation() {
let id = NodeId::new(Language::Cpp, "src/main.cpp", "main");
assert_eq!(id.language, Language::Cpp);
assert_eq!(id.file.as_ref(), "src/main.cpp");
assert_eq!(id.qualified_name.as_ref(), "main");
}
#[test]
fn test_node_id_display() {
let id = NodeId::new(Language::Python, "api.py", "User.authenticate");
assert_eq!(id.to_string(), "py:api.py:User.authenticate");
}
#[test]
fn test_node_id_hash() {
use std::collections::HashSet;
let id1 = NodeId::new(Language::JavaScript, "api.js", "fetchUsers");
let id2 = NodeId::new(Language::JavaScript, "api.js", "fetchUsers");
let id3 = NodeId::new(Language::JavaScript, "api.js", "createUser");
let mut set = HashSet::new();
set.insert(id1.clone());
set.insert(id2.clone());
set.insert(id3.clone());
assert_eq!(set.len(), 2); }
#[test]
fn test_node_id_clone_cheap() {
let id1 = NodeId::new(Language::Cpp, "src/utils.cpp", "std::vector::push_back");
let id2 = id1.clone();
assert_eq!(Arc::as_ptr(&id1.file), Arc::as_ptr(&id2.file));
assert_eq!(
Arc::as_ptr(&id1.qualified_name),
Arc::as_ptr(&id2.qualified_name)
);
}
#[test]
fn test_symbol_name_extraction() {
let id1 = NodeId::new(Language::Cpp, "main.cpp", "std::vector::push_back");
assert_eq!(id1.symbol_name(), "push_back");
let id2 = NodeId::new(Language::Python, "api.py", "User.authenticate");
assert_eq!(id2.symbol_name(), "authenticate");
let id3 = NodeId::new(Language::JavaScript, "api.js", "fetchUsers");
assert_eq!(id3.symbol_name(), "fetchUsers");
}
#[test]
fn test_span_creation() {
let span = Span::new(Position::new(10, 0), Position::new(20, 1));
assert_eq!(span.start.line, 10);
assert_eq!(span.end.line, 20);
}
#[test]
fn test_language_display() {
assert_eq!(Language::Cpp.to_string(), "cpp");
assert_eq!(Language::JavaScript.to_string(), "js");
assert_eq!(Language::Python.to_string(), "py");
assert_eq!(Language::Ruby.to_string(), "ruby");
assert_eq!(Language::Php.to_string(), "php");
assert_eq!(Language::Swift.to_string(), "swift");
assert_eq!(Language::Kotlin.to_string(), "kotlin");
assert_eq!(Language::Scala.to_string(), "scala");
assert_eq!(Language::Http.to_string(), "http");
}
#[test]
fn test_language_from_id() {
assert_eq!(Language::from_id("javascript"), Some(Language::JavaScript));
assert_eq!(Language::from_id("js"), Some(Language::JavaScript));
assert_eq!(Language::from_id("c#"), Some(Language::CSharp));
assert_eq!(Language::from_id("rb"), Some(Language::Ruby));
assert_eq!(Language::from_id("json"), Some(Language::Json));
assert_eq!(Language::from_id("unknown"), None);
}
}