use std::collections::HashMap;
use std::path::PathBuf;
use lsp_types::{Diagnostic, Uri};
use super::super::conversions::convert_diagnostic;
use crate::lsp::global_state::StateSnapshot;
use crate::lsp::uri_ext::UriExt;
pub(crate) type Publish = (Uri, Option<i32>, Vec<Diagnostic>);
pub(crate) fn compute_publishes_with_dependents(
snap: &StateSnapshot,
uri: &Uri,
run_external: bool,
) -> Vec<Publish> {
let mut publishes = Vec::new();
if let Some(state) = snap.document_state(uri)
&& let Some(path) = state.path.as_ref()
{
let graph =
crate::salsa::project_structure(snap.db(), state.salsa_file, state.salsa_config)
.clone();
for dependent in graph.dependents(path, None) {
if let Some(dep_uri) = Uri::from_file_path(&dependent) {
publishes.extend(compute_publishes(snap, &dep_uri, false));
}
}
}
publishes.extend(compute_publishes(snap, uri, run_external));
publishes
}
pub(crate) fn compute_publishes(
snap: &StateSnapshot,
uri: &Uri,
run_external: bool,
) -> Vec<Publish> {
log::debug!(
"compute_publishes uri={} run_external={}",
uri.as_str(),
run_external
);
let Some(doc_state) = snap.document_state(uri) else {
log::warn!("Document not found for lint: {}", uri.as_str());
return Vec::new();
};
let text = doc_state.salsa_file.content_or_empty(snap.db()).to_string();
let lint_plan =
crate::salsa::built_in_lint_plan(snap.db(), doc_state.salsa_file, doc_state.salsa_config)
.clone();
let mut panache_diagnostics = lint_plan.diagnostics;
let external_jobs = lint_plan.external_jobs;
#[cfg(not(target_arch = "wasm32"))]
if run_external && !external_jobs.is_empty() {
let registry = crate::linter::external_linters::ExternalLinterRegistry::new();
for job in &external_jobs {
match crate::linter::external_linters_sync::run_linter_sync(
&job.linter_name,
&job.language,
&job.content,
&text,
®istry,
Some(job.mappings.as_slice()),
) {
Ok(diags) => panache_diagnostics.extend(diags),
Err(e) => log::warn!("External linter failed: {e}"),
}
}
panache_diagnostics.sort_by_key(|d| (d.location.line, d.location.column));
}
let own_diagnostics: Vec<Diagnostic> = panache_diagnostics
.iter()
.map(|d| convert_diagnostic(d, &text))
.collect();
let root_path = uri.to_file_path().map(|p| p.into_owned());
let mut by_path: HashMap<PathBuf, Vec<crate::linter::diagnostics::Diagnostic>> = HashMap::new();
for entry in crate::salsa::project_graph::accumulated::<crate::salsa::GraphDiagnostic>(
snap.db(),
doc_state.salsa_file,
doc_state.salsa_config,
) {
by_path
.entry(entry.0.path.clone())
.or_default()
.push(entry.0.diagnostic.clone());
}
if let Some(root_path) = root_path {
by_path.entry(root_path).or_default();
}
let mut publishes = Vec::new();
let mut published_root = false;
for (path, diags) in by_path {
let target_uri = Uri::from_file_path(&path).unwrap_or_else(|| uri.clone());
let target_text = if target_uri == *uri {
text.clone()
} else {
let Some(target_state) = snap.document_state(&target_uri) else {
continue;
};
target_state
.salsa_file
.content_or_empty(snap.db())
.to_string()
};
let mapped: Vec<Diagnostic> = diags
.iter()
.map(|d| convert_diagnostic(d, &target_text))
.collect();
if target_uri == *uri {
let mut merged = own_diagnostics.clone();
merged.extend(mapped);
publishes.push((uri.clone(), None, merged));
published_root = true;
} else {
publishes.push((target_uri, None, mapped));
}
}
if !published_root {
publishes.push((uri.clone(), None, own_diagnostics));
}
publishes
}