rustcodegraph 1.0.1

Local-first code intelligence graph and CLI for AI agents
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};

use rustcodegraph::{CodeGraph, IndexOptions};

static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);

pub(super) struct TempDir {
    path: PathBuf,
}

impl TempDir {
    pub(super) fn new(prefix: &str) -> Self {
        for _ in 0..100 {
            let nanos = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .expect("system time should be after Unix epoch")
                .as_nanos();
            let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
            let path = std::env::temp_dir()
                .join(format!("{prefix}{}-{nanos}-{counter}", std::process::id()));
            match fs::create_dir(&path) {
                Ok(()) => return Self { path },
                Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => continue,
                Err(err) => panic!("failed to create temp dir {}: {err}", path.display()),
            }
        }

        panic!("failed to create unique temp dir with prefix {prefix}");
    }

    pub(super) fn path(&self) -> &Path {
        &self.path
    }

    pub(super) fn write(&self, relative_path: &str, contents: &str) {
        let path = self.path.join(relative_path);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent)
                .unwrap_or_else(|err| panic!("failed to create {}: {err}", parent.display()));
        }
        fs::write(&path, contents)
            .unwrap_or_else(|err| panic!("failed to write {}: {err}", path.display()));
    }
}

impl Drop for TempDir {
    fn drop(&mut self) {
        if self.path.exists() {
            let _ = fs::remove_dir_all(&self.path);
        }
    }
}

pub(super) fn init_indexed_project(temp: &TempDir) {
    let mut cg = CodeGraph::init_sync(temp.path()).expect("CodeGraph should initialize");
    let result = cg.index_all(IndexOptions::default());
    assert!(result.success, "indexing should succeed");
    cg.close();
}

pub(super) fn init_original_eval_fixture() -> TempDir {
    let temp = TempDir::new("cg-eval-original-");

    temp.write(
        "server/rest/RestController.java",
        r#"
package org.elasticsearch.rest;

class RestController {
    private final BaseRestHandler handler = new BaseRestHandler();

    RestStatus dispatchRequest(RestRequest request) {
        return handler.handleRequest(request);
    }
}

interface RestHandler {
    RestStatus handleRequest(RestRequest request);
}

class BaseRestHandler implements RestHandler {
    public RestStatus handleRequest(RestRequest request) {
        return RestStatus.OK;
    }
}

class RestRequest {
    private final String path;

    RestRequest(String path) {
        this.path = path;
    }
}

enum RestStatus {
    OK,
    ERROR
}
"#,
    );

    temp.write(
        "search/SearchExecutionService.java",
        r#"
package org.elasticsearch.search;

class SearchExecutionService {
    SearchShardsGroup executeSearch(ShardSearchRequest shardRequest) {
        SearchShardsRequest request = new SearchShardsRequest();
        return request.shardsFor(shardRequest);
    }
}

class ShardSearchRequest {
    int shardId() {
        return 1;
    }
}

class SearchShardsRequest {
    SearchShardsGroup shardsFor(ShardSearchRequest request) {
        return new SearchShardsGroup(request.shardId());
    }
}

class SearchShardsGroup {
    private final int shardId;

    SearchShardsGroup(int shardId) {
        this.shardId = shardId;
    }
}

class SearchPhaseExecutionException extends RuntimeException {
}
"#,
    );

    temp.write(
        "bulk/TransportBulkAction.java",
        r#"
package org.elasticsearch.action.bulk;

class TransportBulkAction {
    BulkResponse execute(BulkRequest request) {
        return new BulkResponse(request.size());
    }
}

class BulkRequest {
    int size() {
        return 1;
    }
}

class BulkResponse {
    private final int itemCount;

    BulkResponse(int itemCount) {
        this.itemCount = itemCount;
    }
}
"#,
    );

    temp.write(
        "cluster/routing/allocation/AllocationService.java",
        r#"
package org.elasticsearch.cluster.routing.allocation;

class AllocationService {
    private final BalancedShardsAllocator allocator = new BalancedShardsAllocator();

    void reroute() {
        allocator.allocate();
    }
}

class BalancedShardsAllocator {
    void allocate() {
    }
}
"#,
    );

    temp.write(
        "transport/TransportService.java",
        r#"
package org.elasticsearch.transport;

class TransportService {
    private final SearchTransportService searchTransportService = new SearchTransportService();

    void sendRequest(String action, ActionListener listener) {
        searchTransportService.search(action, listener);
    }
}

class SearchTransportService {
    void search(String action, ActionListener listener) {
        listener.onResponse(action);
    }
}

interface ActionListener {
    void onResponse(String response);
}
"#,
    );

    temp.write(
        "index/engine/Engine.java",
        r#"
package org.elasticsearch.index.engine;

class Engine {
    static class Index {
    }

    void index(Index operation) {
    }
}

class InternalEngine extends Engine {
    void indexDocument(Index operation) {
        index(operation);
    }
}

class ReadOnlyEngine extends Engine {
    boolean isReadOnly() {
        return true;
    }
}
"#,
    );

    init_indexed_project(&temp);
    temp
}

pub(super) fn cleanup_report(path: Option<&Path>) {
    if let Some(path) = path {
        let _ = fs::remove_file(path);
        if let Some(parent) = path.parent() {
            let _ = fs::remove_dir(parent);
        }
    }
}