Skip to main content

weavatrix_rust/analyzer/
mod.rs

1//! Repository scan, extraction, resolution, and snapshot orchestration.
2
3mod imports;
4mod mounts;
5mod pipeline;
6mod references;
7mod state;
8mod support;
9
10use crate::language::LanguageRegistry;
11use crate::model::{Result, Snapshot};
12use pipeline::parse_parallel;
13use state::{AnalysisState, parse_source};
14use std::path::Path;
15use support::{canonical_repository, capabilities};
16
17#[derive(Debug, Clone)]
18pub struct AnalyzerConfig {
19    pub max_file_bytes: u64,
20}
21
22impl Default for AnalyzerConfig {
23    fn default() -> Self {
24        Self {
25            max_file_bytes: 1_500_000,
26        }
27    }
28}
29
30pub struct Analyzer {
31    config: AnalyzerConfig,
32    languages: LanguageRegistry,
33}
34
35#[derive(Debug, Clone)]
36pub struct SourceInput {
37    pub path: String,
38    pub bytes: Vec<u8>,
39    pub content_hash: Option<String>,
40}
41
42impl Default for Analyzer {
43    fn default() -> Self {
44        Self::new(AnalyzerConfig::default())
45    }
46}
47
48impl Analyzer {
49    #[must_use]
50    pub fn new(config: AnalyzerConfig) -> Self {
51        Self {
52            config,
53            languages: LanguageRegistry::default(),
54        }
55    }
56
57    #[must_use]
58    pub fn supports_path(&self, path: &str) -> bool {
59        Path::new(path)
60            .extension()
61            .and_then(|value| value.to_str())
62            .map(str::to_ascii_lowercase)
63            .is_some_and(|extension| self.languages.adapter_for_extension(&extension).is_some())
64    }
65
66    #[must_use]
67    pub const fn max_file_bytes(&self) -> u64 {
68        self.config.max_file_bytes
69    }
70
71    /// Analyzes a repository into a deterministic, evidence-carrying snapshot.
72    ///
73    /// # Errors
74    ///
75    /// Returns an error when the repository cannot be read, an adapter cannot
76    /// initialize, or normalized facts violate graph integrity.
77    pub fn analyze(&self, repository: impl AsRef<Path>) -> Result<Snapshot> {
78        self.analyze_with_report(repository)
79            .map(|(snapshot, _)| snapshot)
80    }
81
82    /// Analyzes an immutable set of source blobs without materializing a tree.
83    ///
84    /// This is the bridge used by the Git module for revision-aware graph
85    /// comparisons. The repository path only supplies stable repository
86    /// identity; every analyzed byte comes from `sources`.
87    ///
88    /// # Errors
89    ///
90    /// Returns parser or graph validation failures.
91    pub fn analyze_sources(
92        &self,
93        repository: impl AsRef<Path>,
94        revision: impl Into<String>,
95        sources: impl IntoIterator<Item = SourceInput>,
96    ) -> Result<Snapshot> {
97        let repository = canonical_repository(repository.as_ref())?;
98        let sources = sources
99            .into_iter()
100            .filter(|source| {
101                u64::try_from(source.bytes.len()).unwrap_or(u64::MAX) <= self.config.max_file_bytes
102            })
103            .collect::<Vec<_>>();
104        let mut parsed = parse_parallel(sources.len(), |index| {
105            let source = &sources[index];
106            parse_source(
107                &source.path,
108                &source.bytes,
109                source.content_hash.as_deref(),
110                &self.languages,
111            )
112        })?;
113        mounts::apply(&mut parsed);
114        let (node_hint, edge_hint) = AnalysisState::expected(&parsed);
115        let mut state = AnalysisState::with_capacity(&repository, node_hint, edge_hint)?;
116        for item in parsed {
117            state.integrate(item)?;
118        }
119        state.resolve_references()?;
120        state.into_snapshot(&repository, revision.into(), capabilities(&self.languages))
121    }
122
123    /// Analyzes a repository and serializes the snapshot as JSON.
124    ///
125    /// # Errors
126    ///
127    /// Returns any analysis error or a JSON serialization error.
128    pub fn analyze_json(&self, repository: impl AsRef<Path>, pretty: bool) -> Result<String> {
129        let snapshot = self.analyze(repository)?;
130        if pretty {
131            Ok(blazingly_json::to_string_pretty(&snapshot)?)
132        } else {
133            Ok(blazingly_json::to_string(&snapshot)?)
134        }
135    }
136
137    /// Analyzes a repository and serializes a JS Weavatrix-compatible graph.
138    ///
139    /// # Errors
140    ///
141    /// Returns any analysis error or a JSON serialization error.
142    pub fn analyze_legacy_json(
143        &self,
144        repository: impl AsRef<Path>,
145        pretty: bool,
146    ) -> Result<String> {
147        Ok(self.analyze(repository)?.legacy_json(pretty)?)
148    }
149}