impactsense-parser 0.1.1

Multi-language static analysis: parse codebases into an in-memory dependency graph for impact analysis
Documentation
//! Pure extraction from parsed files into [`ProjectIr`] (no Neo4j).

use std::path::Path;

use thiserror::Error;

use crate::compress::CompressError;
use crate::graph::{build_project_ir, enrich_project_ir_code_bytes, GraphPersistenceOptions};
use crate::ir::ProjectIr;
use crate::pipeline::ScanOptions;
use crate::scanner::{scan_and_parse, FileScanConfig, ScannerError};

pub use crate::graph::ExtractOptions;

impl From<&ScanOptions> for crate::graph::ExtractOptions {
    fn from(opts: &ScanOptions) -> Self {
        Self {
            verbose_imports: opts.graph.verbose_imports,
            max_parse_warnings_per_file: opts.graph.max_parse_warnings_per_file,
            compressor: opts.graph.compressor.clone(),
        }
    }
}

impl From<&GraphPersistenceOptions> for crate::graph::ExtractOptions {
    fn from(opts: &GraphPersistenceOptions) -> Self {
        Self {
            verbose_imports: opts.verbose_imports,
            max_parse_warnings_per_file: opts.max_parse_warnings_per_file,
            compressor: opts.compressor.clone(),
        }
    }
}

#[derive(Debug, Error)]
pub enum ExtractError {
    #[error("scan/parse failed: {0}")]
    Scanner(#[from] ScannerError),
    #[error("code compression failed: {0}")]
    Compress(#[from] CompressError),
}

/// Build a full project IR from an already-parsed file batch.
pub fn build_project_ir_from_files(
    root: &Path,
    files: &[crate::scanner::ParsedFile],
    options: &ExtractOptions,
) -> ProjectIr {
    build_project_ir(root, files, options)
}

/// Scan a repository root and build project IR (includes `code_bytes` when compression is enabled).
pub fn scan_and_build_ir(
    root: &Path,
    options: &ExtractOptions,
    scan: &ScanOptions,
) -> Result<ProjectIr, ExtractError> {
    let mut config = FileScanConfig::new(root);
    config.follow_symlinks = scan.follow_symlinks;
    config.max_file_size = scan.max_file_size;
    let files = scan_and_parse(&config)?;
    let mut ir = build_project_ir_from_files(root, &files, options);
    run_ir_compression(&mut ir, root, &files, &options.compressor)?;
    Ok(ir)
}

fn run_ir_compression(
    ir: &mut ProjectIr,
    root: &Path,
    files: &[crate::scanner::ParsedFile],
    config: &crate::graph::CompressorConfig,
) -> Result<(), CompressError> {
    if !config.enabled {
        return Ok(());
    }
    if let Ok(handle) = tokio::runtime::Handle::try_current() {
        return handle.block_on(enrich_project_ir_code_bytes(ir, root, files, config));
    }
    let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime for compression");
    rt.block_on(enrich_project_ir_code_bytes(ir, root, files, config))
}

/// Async variant of [`scan_and_build_ir`] for callers already on a Tokio runtime (e.g. MCP).
pub async fn scan_and_build_ir_async(
    root: &Path,
    options: &ExtractOptions,
    scan: &ScanOptions,
) -> Result<ProjectIr, ExtractError> {
    let mut config = FileScanConfig::new(root);
    config.follow_symlinks = scan.follow_symlinks;
    config.max_file_size = scan.max_file_size;
    let files = scan_and_parse(&config)?;
    let mut ir = build_project_ir_from_files(root, &files, options);
    enrich_project_ir_code_bytes(&mut ir, root, &files, &options.compressor).await?;
    Ok(ir)
}

/// Parse specific targets and build IR delta (incremental).
pub fn parse_files_to_ir(
    root: &Path,
    parse_targets: &[String],
    options: &ExtractOptions,
    scan: &ScanOptions,
) -> Result<ProjectIr, ExtractError> {
    let mut config = FileScanConfig::new(root);
    config.follow_symlinks = scan.follow_symlinks;
    config.max_file_size = scan.max_file_size;
    let paths: Vec<_> = parse_targets.iter().map(std::path::PathBuf::from).collect();
    let files = crate::scanner_incremental::scan_and_parse_incremental_vector(&config, &paths)?;
    let mut ir = build_project_ir_from_files(root, &files, options);
    run_ir_compression(&mut ir, root, &files, &options.compressor)?;
    Ok(ir)
}