1mod diagnostics;
9mod discover;
10
11use std::collections::HashMap;
12use std::io;
13
14pub use brink_analyzer::AnalysisResult;
15pub use brink_db::ProjectDb;
16pub use brink_ir::FileId;
17pub use diagnostics::DiagnosticReport;
18pub use discover::DiscoverError;
19
20pub struct Driver {
22 db: ProjectDb,
23 analysis: Option<AnalysisResult>,
24}
25
26impl Driver {
27 pub fn new() -> Self {
29 Self {
30 db: ProjectDb::new(),
31 analysis: None,
32 }
33 }
34
35 pub fn from_db(db: ProjectDb) -> Self {
37 Self { db, analysis: None }
38 }
39
40 pub fn db(&self) -> &ProjectDb {
42 &self.db
43 }
44
45 pub fn db_mut(&mut self) -> &mut ProjectDb {
50 self.analysis = None;
51 &mut self.db
52 }
53
54 pub fn into_db(self) -> ProjectDb {
56 self.db
57 }
58
59 pub fn discover<F>(&mut self, entry: &str, read_file: F) -> Result<(), DiscoverError>
63 where
64 F: FnMut(&str) -> Result<String, io::Error>,
65 {
66 discover::discover(&mut self.db, entry, &mut { read_file })
67 }
68
69 #[expect(
73 clippy::expect_used,
74 reason = "analysis is always Some after the if-block above"
75 )]
76 pub fn analyze(&mut self) -> &AnalysisResult {
77 if self.analysis.is_none() {
78 let inputs = self.db.analysis_inputs();
79 let files: Vec<_> = inputs
80 .iter()
81 .map(|(id, hir, manifest)| (*id, hir, manifest))
82 .collect();
83
84 tracing::info!(files = files.len(), "running cross-file analysis");
85 self.analysis = Some(brink_analyzer::analyze(&files));
86 }
87 self.analysis.as_ref().expect("just set above")
88 }
89
90 pub fn analyze_project(&self, file_ids: &[FileId]) -> AnalysisResult {
92 let inputs = self.db.analysis_inputs_for(file_ids);
93 let file_refs: Vec<_> = inputs
94 .iter()
95 .map(|(id, hir, manifest)| (*id, hir, manifest))
96 .collect();
97 brink_analyzer::analyze(&file_refs)
98 }
99
100 pub fn analysis_inputs_for(
102 &self,
103 file_ids: &[FileId],
104 ) -> Vec<(FileId, brink_ir::HirFile, brink_ir::SymbolManifest)> {
105 self.db.analysis_inputs_for(file_ids)
106 }
107
108 pub fn analysis_inputs(&self) -> Vec<(FileId, brink_ir::HirFile, brink_ir::SymbolManifest)> {
110 self.db.analysis_inputs()
111 }
112
113 pub fn compute_projects(&self) -> Vec<(FileId, Vec<FileId>)> {
117 self.db.compute_projects()
118 }
119
120 pub fn file_ids_topo(&self, entry: FileId) -> Vec<FileId> {
122 self.db.file_ids_topo(entry)
123 }
124
125 pub fn file_metadata(&self) -> Vec<(FileId, String, String)> {
127 self.db.file_metadata()
128 }
129
130 pub fn collect_diagnostics(
134 &self,
135 analysis: &AnalysisResult,
136 entry: Option<FileId>,
137 ) -> DiagnosticReport {
138 diagnostics::collect_diagnostics(&self.db, analysis, entry)
139 }
140
141 pub fn lir_inputs(
147 &self,
148 entry: FileId,
149 ) -> (Vec<(FileId, &brink_ir::HirFile)>, HashMap<FileId, String>) {
150 let ids = self.file_ids_topo(entry);
151 let files: Vec<_> = ids
152 .into_iter()
153 .filter_map(|id| self.db.hir(id).map(|hir| (id, hir)))
154 .collect();
155 let paths: HashMap<_, _> = files
156 .iter()
157 .filter_map(|(id, _)| self.db.file_path(*id).map(|p| (*id, p.to_string())))
158 .collect();
159 (files, paths)
160 }
161}
162
163impl Default for Driver {
164 fn default() -> Self {
165 Self::new()
166 }
167}