1mod diagnostics;
9mod discover;
10
11use std::collections::HashMap;
12use std::io;
13
14pub use brink_analyzer::{AnalysisOptions, 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 analysis_options: AnalysisOptions,
25}
26
27impl Driver {
28 pub fn new() -> Self {
30 Self {
31 db: ProjectDb::new(),
32 analysis: None,
33 analysis_options: AnalysisOptions::default(),
34 }
35 }
36
37 pub fn from_db(db: ProjectDb) -> Self {
39 Self {
40 db,
41 analysis: None,
42 analysis_options: AnalysisOptions::default(),
43 }
44 }
45
46 pub fn set_analysis_options(&mut self, options: AnalysisOptions) {
50 self.analysis_options = options;
51 self.analysis = None;
52 }
53
54 pub fn db(&self) -> &ProjectDb {
56 &self.db
57 }
58
59 pub fn db_mut(&mut self) -> &mut ProjectDb {
64 self.analysis = None;
65 &mut self.db
66 }
67
68 pub fn into_db(self) -> ProjectDb {
70 self.db
71 }
72
73 pub fn discover<F>(&mut self, entry: &str, read_file: F) -> Result<(), DiscoverError>
77 where
78 F: FnMut(&str) -> Result<String, io::Error>,
79 {
80 discover::discover(&mut self.db, entry, &mut { read_file })
81 }
82
83 #[expect(
87 clippy::expect_used,
88 reason = "analysis is always Some after the if-block above"
89 )]
90 pub fn analyze(&mut self) -> &AnalysisResult {
91 if self.analysis.is_none() {
92 let inputs = self.db.analysis_inputs();
93 let files: Vec<_> = inputs
94 .iter()
95 .map(|(id, hir, manifest)| (*id, hir, manifest))
96 .collect();
97
98 tracing::info!(files = files.len(), "running cross-file analysis");
99 self.analysis = Some(brink_analyzer::analyze_with_options(
100 &files,
101 &self.analysis_options,
102 ));
103 }
104 self.analysis.as_ref().expect("just set above")
105 }
106
107 pub fn analyze_project(&self, file_ids: &[FileId]) -> AnalysisResult {
109 let inputs = self.db.analysis_inputs_for(file_ids);
110 let file_refs: Vec<_> = inputs
111 .iter()
112 .map(|(id, hir, manifest)| (*id, hir, manifest))
113 .collect();
114 brink_analyzer::analyze(&file_refs)
115 }
116
117 pub fn analysis_inputs_for(
119 &self,
120 file_ids: &[FileId],
121 ) -> Vec<(FileId, brink_ir::HirFile, brink_ir::SymbolManifest)> {
122 self.db.analysis_inputs_for(file_ids)
123 }
124
125 pub fn analysis_inputs(&self) -> Vec<(FileId, brink_ir::HirFile, brink_ir::SymbolManifest)> {
127 self.db.analysis_inputs()
128 }
129
130 pub fn compute_projects(&self) -> Vec<(FileId, Vec<FileId>)> {
134 self.db.compute_projects()
135 }
136
137 pub fn file_ids_topo(&self, entry: FileId) -> Vec<FileId> {
139 self.db.file_ids_topo(entry)
140 }
141
142 pub fn file_metadata(&self) -> Vec<(FileId, String, String)> {
144 self.db.file_metadata()
145 }
146
147 pub fn collect_diagnostics(
151 &self,
152 analysis: &AnalysisResult,
153 entry: Option<FileId>,
154 ) -> DiagnosticReport {
155 diagnostics::collect_diagnostics(&self.db, analysis, entry)
156 }
157
158 pub fn lir_inputs(
164 &self,
165 entry: FileId,
166 ) -> (Vec<(FileId, &brink_ir::HirFile)>, HashMap<FileId, String>) {
167 let ids = self.file_ids_topo(entry);
168 let files: Vec<_> = ids
169 .into_iter()
170 .filter_map(|id| self.db.hir(id).map(|hir| (id, hir)))
171 .collect();
172 let paths: HashMap<_, _> = files
173 .iter()
174 .filter_map(|(id, _)| self.db.file_path(*id).map(|p| (*id, p.to_string())))
175 .collect();
176 (files, paths)
177 }
178}
179
180impl Default for Driver {
181 fn default() -> Self {
182 Self::new()
183 }
184}