1use std::collections::HashSet;
4use std::sync::Arc;
5
6use anyhow::Error;
7use futures::future::BoxFuture;
8use nonempty::NonEmpty;
9use tracing::warn;
10use wdl_analysis::Analyzer;
11use wdl_analysis::DiagnosticsConfig;
12use wdl_analysis::ProgressKind;
13use wdl_analysis::Validator;
14use wdl_lint::Linter;
15
16mod results;
17mod source;
18
19pub use results::AnalysisResults;
20pub use source::Source;
21
22type InitCb = Box<dyn Fn() + 'static>;
24
25type ProgressCb =
27 Box<dyn Fn(ProgressKind, usize, usize) -> BoxFuture<'static, ()> + Send + Sync + 'static>;
28
29pub struct Analysis {
31 sources: Vec<Source>,
35
36 exceptions: HashSet<String>,
38
39 lint: bool,
41
42 init: InitCb,
44
45 progress: ProgressCb,
47}
48
49impl Analysis {
50 pub fn add_source(mut self, source: Source) -> Self {
52 self.sources.push(source);
53 self
54 }
55
56 pub fn extend_sources(mut self, source: impl IntoIterator<Item = Source>) -> Self {
58 self.sources.extend(source);
59 self
60 }
61
62 pub fn add_exception(mut self, rule: impl Into<String>) -> Self {
64 self.exceptions.insert(rule.into());
65 self
66 }
67
68 pub fn extend_exceptions(mut self, rules: impl IntoIterator<Item = String>) -> Self {
70 self.exceptions.extend(rules);
71 self
72 }
73
74 pub fn lint(mut self, value: bool) -> Self {
76 self.lint = value;
77 self
78 }
79
80 pub fn init<F>(mut self, init: F) -> Self
82 where
83 F: Fn() + 'static,
84 {
85 self.init = Box::new(init);
86 self
87 }
88
89 pub fn progress<F>(mut self, progress: F) -> Self
91 where
92 F: Fn(ProgressKind, usize, usize) -> BoxFuture<'static, ()> + Send + Sync + 'static,
93 {
94 self.progress = Box::new(progress);
95 self
96 }
97
98 pub async fn run(self) -> std::result::Result<AnalysisResults, NonEmpty<Arc<Error>>> {
100 warn_unknown_rules(&self.exceptions);
101 let config = get_diagnostics_config(&self.exceptions);
102
103 (self.init)();
104
105 let validator = Box::new(move || {
106 let mut validator = Validator::default();
107
108 if self.lint {
109 let visitor = get_lint_visitor(&self.exceptions);
110 validator.add_visitor(visitor);
111 }
112
113 validator
114 });
115
116 let mut analyzer = Analyzer::new_with_validator(
117 config,
118 move |_, kind, count, total| (self.progress)(kind, count, total),
119 validator,
120 );
121
122 for source in self.sources {
123 if let Err(error) = source.register(&mut analyzer).await {
124 return Err(NonEmpty::new(Arc::new(error)));
125 }
126 }
127
128 let results = analyzer
129 .analyze(())
130 .await
131 .map_err(|error| NonEmpty::new(Arc::new(error)))?;
132
133 AnalysisResults::try_new(results)
134 }
135}
136
137impl Default for Analysis {
138 fn default() -> Self {
139 Self {
140 sources: Default::default(),
141 exceptions: Default::default(),
142 lint: Default::default(),
143 init: Box::new(|| {}),
144 progress: Box::new(|_, _, _| Box::pin(async {})),
145 }
146 }
147}
148
149fn warn_unknown_rules(exceptions: &HashSet<String>) {
151 let mut names = wdl_analysis::rules()
152 .iter()
153 .map(|rule| rule.id().to_owned())
154 .collect::<Vec<_>>();
155
156 names.extend(wdl_lint::rules().iter().map(|rule| rule.id().to_owned()));
157
158 let mut unknown = exceptions
159 .iter()
160 .filter(|rule| !names.iter().any(|name| name.eq_ignore_ascii_case(rule)))
161 .map(|rule| format!("`{rule}`"))
162 .collect::<Vec<_>>();
163
164 if !unknown.is_empty() {
165 unknown.sort();
166
167 warn!(
168 "ignoring unknown excepted rule{s}: {rules}",
169 s = if unknown.len() == 1 { "" } else { "s" },
170 rules = unknown.join(", ")
171 );
172 }
173}
174
175fn get_diagnostics_config(exceptions: &HashSet<String>) -> DiagnosticsConfig {
178 DiagnosticsConfig::new(wdl_analysis::rules().into_iter().filter(|rule| {
179 !exceptions
180 .iter()
181 .any(|exception| exception.eq_ignore_ascii_case(rule.id()))
182 }))
183}
184
185fn get_lint_visitor(exceptions: &HashSet<String>) -> Linter {
187 Linter::new(wdl_lint::rules().into_iter().filter(|rule| {
188 !exceptions
189 .iter()
190 .any(|exception| exception.eq_ignore_ascii_case(rule.id()))
191 }))
192}