use std::collections::HashSet;
use std::sync::Arc;
use anyhow::Error;
use futures::future::BoxFuture;
use nonempty::NonEmpty;
use tracing::warn;
use wdl_analysis::Analyzer;
use wdl_analysis::DiagnosticsConfig;
use wdl_analysis::ProgressKind;
use wdl_analysis::Validator;
use wdl_lint::Linter;
mod results;
mod source;
pub use results::AnalysisResults;
pub use source::Source;
type InitCb = Box<dyn Fn() + 'static>;
type ProgressCb =
Box<dyn Fn(ProgressKind, usize, usize) -> BoxFuture<'static, ()> + Send + Sync + 'static>;
pub struct Analysis {
sources: Vec<Source>,
exceptions: HashSet<String>,
lint: bool,
init: InitCb,
progress: ProgressCb,
}
impl Analysis {
pub fn add_source(mut self, source: Source) -> Self {
self.sources.push(source);
self
}
pub fn extend_sources(mut self, source: impl IntoIterator<Item = Source>) -> Self {
self.sources.extend(source);
self
}
pub fn add_exception(mut self, rule: impl Into<String>) -> Self {
self.exceptions.insert(rule.into());
self
}
pub fn extend_exceptions(mut self, rules: impl IntoIterator<Item = String>) -> Self {
self.exceptions.extend(rules);
self
}
pub fn lint(mut self, value: bool) -> Self {
self.lint = value;
self
}
pub fn init<F>(mut self, init: F) -> Self
where
F: Fn() + 'static,
{
self.init = Box::new(init);
self
}
pub fn progress<F>(mut self, progress: F) -> Self
where
F: Fn(ProgressKind, usize, usize) -> BoxFuture<'static, ()> + Send + Sync + 'static,
{
self.progress = Box::new(progress);
self
}
pub async fn run(self) -> std::result::Result<AnalysisResults, NonEmpty<Arc<Error>>> {
warn_unknown_rules(&self.exceptions);
let config = get_diagnostics_config(&self.exceptions);
(self.init)();
let validator = Box::new(move || {
let mut validator = Validator::default();
if self.lint {
let visitor = get_lint_visitor(&self.exceptions);
validator.add_visitor(visitor);
}
validator
});
let mut analyzer = Analyzer::new_with_validator(
config,
move |_, kind, count, total| (self.progress)(kind, count, total),
validator,
);
for source in self.sources {
if let Err(error) = source.register(&mut analyzer).await {
return Err(NonEmpty::new(Arc::new(error)));
}
}
let results = analyzer
.analyze(())
.await
.map_err(|error| NonEmpty::new(Arc::new(error)))?;
AnalysisResults::try_new(results)
}
}
impl Default for Analysis {
fn default() -> Self {
Self {
sources: Default::default(),
exceptions: Default::default(),
lint: Default::default(),
init: Box::new(|| {}),
progress: Box::new(|_, _, _| Box::pin(async {})),
}
}
}
fn warn_unknown_rules(exceptions: &HashSet<String>) {
let mut names = wdl_analysis::rules()
.iter()
.map(|rule| rule.id().to_owned())
.collect::<Vec<_>>();
names.extend(wdl_lint::rules().iter().map(|rule| rule.id().to_owned()));
let mut unknown = exceptions
.iter()
.filter(|rule| !names.iter().any(|name| name.eq_ignore_ascii_case(rule)))
.map(|rule| format!("`{rule}`"))
.collect::<Vec<_>>();
if !unknown.is_empty() {
unknown.sort();
warn!(
"ignoring unknown excepted rule{s}: {rules}",
s = if unknown.len() == 1 { "" } else { "s" },
rules = unknown.join(", ")
);
}
}
fn get_diagnostics_config(exceptions: &HashSet<String>) -> DiagnosticsConfig {
DiagnosticsConfig::new(wdl_analysis::rules().into_iter().filter(|rule| {
!exceptions
.iter()
.any(|exception| exception.eq_ignore_ascii_case(rule.id()))
}))
}
fn get_lint_visitor(exceptions: &HashSet<String>) -> Linter {
Linter::new(wdl_lint::rules().into_iter().filter(|rule| {
!exceptions
.iter()
.any(|exception| exception.eq_ignore_ascii_case(rule.id()))
}))
}