Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
715 changes: 348 additions & 367 deletions Cargo.lock

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ categories = ["development-tools", "command-line-utilities", "parsing"]
# Config (rust-base-convention)
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
toml = "1.1"
anyhow = "1"
thiserror = "1"
thiserror = "2"

# Utilities
sha2 = "0.10"
sha2 = "0.11"
sha1_smol = "1"
rayon = "1.8"
globset = "0.4"
Expand All @@ -47,19 +47,19 @@ tracing-appender = "0.2"
time = { version = "0.3", features = ["macros", "local-offset", "formatting"] }

# Database
rusqlite = { version = "0.31", features = ["bundled"] }
rusqlite = { version = "0.40", features = ["bundled"] }

# Tree-sitter
tree-sitter = "0.26"
tree-sitter = "0.27"
streaming-iterator = "0.1"

# File watching
notify = "6"
notify = "8"

# Cross-platform IPC (daemon local socket / named pipe). The `tokio` feature
# enables the async local-socket Listener/Stream used by the daemon accept loop.
interprocess = { version = "2.4", features = ["tokio"] }
rustix = { version = "0.38", features = ["process"] }
rustix = { version = "1.1", features = ["process"] }

# CLI
clap = { version = "4.5", features = ["derive"] }
Expand All @@ -69,7 +69,7 @@ clap_complete = "4.5"
indicatif = { version = "0.18", features = ["rayon"] }

# JSONC config editing (preserve comments + key order on agent config writes)
jsonc-parser = { version = "0.26", features = ["cst", "serde"] }
jsonc-parser = { version = "0.34", features = ["cst", "serde"] }

# Self-update from GitHub Releases (mise-style `codegraph self-update`)
# self_update 0.43+ moved to quick-xml ^0.38 (fixes RUSTSEC-2026-0194/0195) and
Expand All @@ -80,10 +80,10 @@ jsonc-parser = { version = "0.26", features = ["cst", "serde"] }
self_update = { version = "0.44", default-features = false, features = ["reqwest", "rustls", "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate"] }

# Benchmarking
criterion = "0.7"
criterion = "0.8"

# Testing (dev-dependencies; not shipped in the binary)
rstest = "0.23"
rstest = "0.27"
assert_fs = "1"
assert_cmd = "2"
predicates = "3"
Expand Down
25 changes: 4 additions & 21 deletions crates/codegraph-cli/src/installer/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,32 +450,15 @@ pub fn to_upstream_json(value: &Value) -> String {
serde_json::to_string_pretty(value).unwrap_or_else(|_| "{}".to_string())
}

// jsonc-parser 0.26 `CstStringLit::new_escaped` escapes only `"`, not `\` or
// control chars, so a Windows path `C:\Users` emits invalid JSON `"C:\Users"`.
// Pre-escape everything JSON-significant EXCEPT `"` (the library owns quotes).
fn escape_for_cst_string(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c => out.push(c),
}
}
out
}

// jsonc-parser's `CstStringLit::new_escaped` escapes `"`, `\` and control
// characters itself (since 0.34; 0.26 escaped only `"`, which made a Windows
// path `C:\Users` invalid JSON), so a string goes in raw.
fn to_cst_input(value: &Value) -> CstInputValue {
match value {
Value::Null => CstInputValue::Null,
Value::Bool(b) => CstInputValue::Bool(*b),
Value::Number(n) => CstInputValue::Number(n.to_string()),
Value::String(s) => CstInputValue::String(escape_for_cst_string(s)),
Value::String(s) => CstInputValue::String(s.clone()),
Value::Array(arr) => CstInputValue::Array(arr.iter().map(to_cst_input).collect()),
Value::Object(map) => CstInputValue::Object(
map.iter()
Expand Down
2 changes: 1 addition & 1 deletion crates/codegraph-daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ rustix = { workspace = true, features = ["net"] }

# Windows process introspection (OpenProcess + GetExitCodeProcess liveness)
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52", features = [
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_System_Threading",
] }
Expand Down
6 changes: 3 additions & 3 deletions crates/codegraph-daemon/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub fn terminate_pid(pid: u32) -> bool {
let Some(pid) = rustix::process::Pid::from_raw(raw) else {
return false;
};
rustix::process::kill_process(pid, rustix::process::Signal::Term).is_ok()
rustix::process::kill_process(pid, rustix::process::Signal::TERM).is_ok()
}

// Windows has no stable getppid; returning 0 makes the ppid-divergence branch
Expand Down Expand Up @@ -120,7 +120,7 @@ pub fn is_process_alive(pid: u32) -> bool {
// proves it exists (treat as alive); any other error means it is gone.
unsafe {
let handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
if handle == 0 {
if handle.is_null() {
return GetLastError() == ERROR_ACCESS_DENIED;
}
let mut code: u32 = 0;
Expand All @@ -145,7 +145,7 @@ pub fn terminate_pid(pid: u32) -> bool {
// Rust-side aliasing. A null handle means the open failed (nothing to kill).
unsafe {
let handle = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if handle == 0 {
if handle.is_null() {
return false;
}
let ok = TerminateProcess(handle, 1);
Expand Down
2 changes: 1 addition & 1 deletion crates/codegraph-daemon/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ impl SessionRegistry {
// triggers). We only issue shutdown(SHUT_RDWR); no ownership is
// taken and the fd is not closed here.
let borrowed = unsafe { BorrowedFd::borrow_raw(fd) };
let _ = rustix::net::shutdown(borrowed, rustix::net::Shutdown::ReadWrite);
let _ = rustix::net::shutdown(borrowed, rustix::net::Shutdown::Both);
}
}
#[cfg(not(unix))]
Expand Down
4 changes: 2 additions & 2 deletions crates/codegraph-extract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ ignore = { workspace = true }
serde_json = { workspace = true }
tracing = { workspace = true }

tree-sitter-arkts = "0.2.0"
tree-sitter-arkts = "0.3.0"
tree-sitter-c = "0.24.2"
tree-sitter-c-sharp = "0.23.5"
tree-sitter-cfml = "0.26.30"
tree-sitter-cpp = "0.23.4"
tree-sitter-css = "0.25.0"
tree-sitter-dart = "0.2.0"
tree-sitter-erlang = "0.19.0"
tree-sitter-erlang = "0.20.0"
tree-sitter-go = "0.25.0"
tree-sitter-gdscript = "6.1.0"
tree-sitter-hcl = "1.1.0"
Expand Down
6 changes: 3 additions & 3 deletions crates/codegraph-extract/src/embedded/vue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ impl<'a> VueExtractor<'a> {
let mut matches = cursor.matches(&query, tree.root_node(), block.content.as_bytes());

while let Some(m) = matches.next() {
for capture in m.captures {
for capture in m.captures() {
let node = capture.node;
let capture_name = query.capture_names()[capture.index as usize];

if capture_name == "func" {
let name_node = m
.captures
.captures()
.iter()
.find(|c| query.capture_names()[c.index as usize] == "func.name")
.unwrap()
Expand Down Expand Up @@ -252,7 +252,7 @@ impl<'a> VueExtractor<'a> {
});
} else if capture_name == "import" {
let source_node = m
.captures
.captures()
.iter()
.find(|c| query.capture_names()[c.index as usize] == "import.source")
.unwrap()
Expand Down
9 changes: 3 additions & 6 deletions crates/codegraph-extract/src/lang/arkts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ impl LanguageSpec for ArkTsSpec {

fn get_visibility(&self, node: Node<'_>) -> Option<String> {
for i in 0..node.child_count() {
let child = node.child(i as u32)?;
let child = node.child(i)?;
if child.kind() == "accessibility_modifier" {
for j in 0..child.child_count() {
let modifier = child.child(j as u32)?;
let modifier = child.child(j)?;
match modifier.kind() {
"public" => return Some("public".to_string()),
"private" => return Some("private".to_string()),
Expand Down Expand Up @@ -201,10 +201,7 @@ impl LanguageSpec for ArkTsSpec {
}

fn has_direct_child_kind(node: Node<'_>, kind: &str) -> bool {
(0..node.child_count()).any(|i| {
node.child(i as u32)
.is_some_and(|child| child.kind() == kind)
})
(0..node.child_count()).any(|i| node.child(i).is_some_and(|child| child.kind() == kind))
}

#[cfg(test)]
Expand Down
6 changes: 2 additions & 4 deletions crates/codegraph-extract/src/lang/dart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,13 @@ impl LanguageSpec for DartSpec {
node.next_named_sibling()
.filter(|next| next.kind() == "function_body")
.is_some_and(|body| {
(0..body.child_count())
.any(|i| body.child(i as u32).is_some_and(|c| c.kind() == "async"))
(0..body.child_count()).any(|i| body.child(i).is_some_and(|c| c.kind() == "async"))
})
}

fn is_static(&self, node: Node<'_>, _source: &str) -> bool {
node.kind() == "method_signature"
&& (0..node.child_count())
.any(|i| node.child(i as u32).is_some_and(|c| c.kind() == "static"))
&& (0..node.child_count()).any(|i| node.child(i).is_some_and(|c| c.kind() == "static"))
}

fn resolve_name(&self, node: Node<'_>, _source: &str) -> Option<String> {
Expand Down
5 changes: 1 addition & 4 deletions crates/codegraph-extract/src/lang/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,5 @@ pub(crate) fn resolve_class_field_body<'tree>(
}

pub(crate) fn has_direct_child_kind(node: Node<'_>, kind: &str) -> bool {
(0..node.child_count()).any(|i| {
node.child(i as u32)
.is_some_and(|child| child.kind() == kind)
})
(0..node.child_count()).any(|i| node.child(i).is_some_and(|child| child.kind() == kind))
}
6 changes: 3 additions & 3 deletions crates/codegraph-extract/src/lang/kotlin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl LanguageSpec for KotlinSpec {
// 'enum' keyword under modifiers > class_modifier, so the modifiers
// child is also scanned; the scan stops at the class body.
for idx in 0..node.child_count() {
let Some(child) = node.child(idx as u32) else {
let Some(child) = node.child(idx) else {
continue;
};
match child.kind() {
Expand All @@ -137,7 +137,7 @@ impl LanguageSpec for KotlinSpec {
// Upstream kotlin.ts:211-231: extension receiver is the user_type before '.'.
let mut found_user_type = None;
for idx in 0..node.child_count() {
let Some(child) = node.child(idx as u32) else {
let Some(child) = node.child(idx) else {
continue;
};
if child.kind() == "user_type" {
Expand Down Expand Up @@ -313,7 +313,7 @@ fn has_descendant_kind(node: Node<'_>, kind: &str) -> bool {
}
for idx in 0..node.child_count() {
if node
.child(idx as u32)
.child(idx)
.is_some_and(|child| has_descendant_kind(child, kind))
{
return true;
Expand Down
2 changes: 1 addition & 1 deletion crates/codegraph-extract/src/lang/objc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ impl LanguageSpec for ObjCSpec {
}

fn is_static(&self, node: Node<'_>, _source: &str) -> bool {
(0..node.child_count()).any(|i| node.child(i as u32).is_some_and(|c| c.kind() == "+"))
(0..node.child_count()).any(|i| node.child(i).is_some_and(|c| c.kind() == "+"))
}

fn extract_import(&self, node: Node<'_>, source: &str) -> Option<ImportInfo> {
Expand Down
4 changes: 2 additions & 2 deletions crates/codegraph-extract/src/lang/pascal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl LanguageSpec for PascalSpec {
while let Some(parent) = current {
if parent.kind() == "declSection" {
for i in 0..parent.child_count() {
if let Some(child) = parent.child(i as u32) {
if let Some(child) = parent.child(i) {
match child.kind() {
"kPublic" | "kPublished" => return Some("public".to_string()),
"kPrivate" => return Some("private".to_string()),
Expand All @@ -118,7 +118,7 @@ impl LanguageSpec for PascalSpec {
}

fn is_static(&self, node: Node<'_>, _source: &str) -> bool {
(0..node.child_count()).any(|i| node.child(i as u32).is_some_and(|c| c.kind() == "kClass"))
(0..node.child_count()).any(|i| node.child(i).is_some_and(|c| c.kind() == "kClass"))
}

fn is_const(&self, node: Node<'_>) -> bool {
Expand Down
12 changes: 5 additions & 7 deletions crates/codegraph-extract/src/lang/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,12 @@ impl LanguageSpec for RustSpec {

fn get_visibility(&self, node: Node<'_>) -> Option<String> {
for i in 0..node.child_count() {
if let Some(child) = node.child(i as u32) {
if let Some(child) = node.child(i) {
if child.kind() == "visibility_modifier" {
return Some(
if (0..child.child_count()).any(|idx| {
child
.child(idx as u32)
.is_some_and(|inner| inner.kind() == "pub")
}) {
if (0..child.child_count())
.any(|idx| child.child(idx).is_some_and(|inner| inner.kind() == "pub"))
{
"public"
} else {
"private"
Expand Down Expand Up @@ -206,7 +204,7 @@ fn root_module(node: Node<'_>, source: &str) -> String {

fn has_child_kind_recursive(node: Node<'_>, kind: &str) -> bool {
(0..node.child_count()).any(|i| {
node.child(i as u32)
node.child(i)
.is_some_and(|child| child.kind() == kind || has_child_kind_recursive(child, kind))
})
}
Expand Down
6 changes: 3 additions & 3 deletions crates/codegraph-extract/src/lang/scala.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,12 @@ impl LanguageSpec for ScalaSpec {
if matches!(child.kind(), "modifiers" | "access_modifier") {
let has_private = (0..child.child_count()).any(|idx| {
child
.child(idx as u32)
.child(idx)
.is_some_and(|inner| inner.kind() == "private")
});
let has_protected = (0..child.child_count()).any(|idx| {
child
.child(idx as u32)
.child(idx)
.is_some_and(|inner| inner.kind() == "protected")
});
if has_private {
Expand All @@ -146,7 +146,7 @@ impl LanguageSpec for ScalaSpec {
child.kind() == "modifiers"
&& (0..child.child_count()).any(|idx| {
child
.child(idx as u32)
.child(idx)
.is_some_and(|inner| inner.kind() == "static")
})
})
Expand Down
2 changes: 1 addition & 1 deletion crates/codegraph-extract/src/lang/solidity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl LanguageSpec for SoliditySpec {
// walk every child and return the first that matches, defaulting
// to `fallback` (upstream `fallbackReceiveName`).
for i in 0..node.child_count() {
if let Some(child) = node.child(i as u32) {
if let Some(child) = node.child(i) {
match child.kind() {
"fallback" => return Some("fallback".to_string()),
"receive" => return Some("receive".to_string()),
Expand Down
10 changes: 5 additions & 5 deletions crates/codegraph-extract/src/lang/swift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl LanguageSpec for SwiftSpec {
// also matches `fileprivate`, so both keyword kinds map to private;
// Swift defaults to internal.
for idx in 0..node.child_count() {
let Some(child) = node.child(idx as u32) else {
let Some(child) = node.child(idx) else {
continue;
};
if child.kind() != "modifiers" {
Expand All @@ -203,7 +203,7 @@ impl LanguageSpec for SwiftSpec {
// swift.ts:101-111 — modifiers containing `static` or `class`
// (class methods are static-dispatch members).
for idx in 0..node.child_count() {
let Some(child) = node.child(idx as u32) else {
let Some(child) = node.child(idx) else {
continue;
};
if child.kind() == "modifiers"
Expand All @@ -219,7 +219,7 @@ impl LanguageSpec for SwiftSpec {
// swift.ts:112-120 — class_declaration covers classes, structs, enums;
// split on the direct keyword child.
for idx in 0..node.child_count() {
let Some(child) = node.child(idx as u32) else {
let Some(child) = node.child(idx) else {
continue;
};
if child.kind() == "struct" {
Expand All @@ -236,7 +236,7 @@ impl LanguageSpec for SwiftSpec {
// swift.ts:121-129 — only `async` inside a modifiers child counts
// (a bare `async` effect token between params and body does not).
for idx in 0..node.child_count() {
let Some(child) = node.child(idx as u32) else {
let Some(child) = node.child(idx) else {
continue;
};
if child.kind() == "modifiers" && has_descendant_kind(child, "async") {
Expand Down Expand Up @@ -287,7 +287,7 @@ fn has_descendant_kind(node: Node<'_>, kind: &str) -> bool {
}
for idx in 0..node.child_count() {
if node
.child(idx as u32)
.child(idx)
.is_some_and(|child| has_descendant_kind(child, kind))
{
return true;
Expand Down
Loading
Loading