weavatrix_rust/analyzer/
mod.rs1mod 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 pub fn analyze(&self, repository: impl AsRef<Path>) -> Result<Snapshot> {
78 self.analyze_with_report(repository)
79 .map(|(snapshot, _)| snapshot)
80 }
81
82 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 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 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}