use std::collections::HashSet;
use std::path::PathBuf;
use cairo_lang_filesystem::db::{FilesGroup, ext_as_virtual};
use cairo_lang_filesystem::ids::{FileId, FileLongId};
use lsp_types::notification::PublishDiagnostics;
use lsp_types::{DiagnosticSeverity, PublishDiagnosticsParams, Url};
use crate::config::Config;
use crate::lang::db::AnalysisDatabase;
use crate::lang::diagnostics::file_diagnostics::FilesDiagnostics;
use crate::lang::diagnostics::project_diagnostics::ProjectDiagnostics;
use crate::lang::lsp::LsProtoGroup;
use crate::project::ConfigsRegistry;
use crate::server::client::Notifier;
use crate::toolchain::scarb::ScarbToolchain;
#[tracing::instrument(skip_all)]
pub fn refresh_diagnostics<'db>(
db: &'db AnalysisDatabase,
config: &Config,
config_registry: &ConfigsRegistry,
batch: Vec<FileId<'db>>,
project_diagnostics: ProjectDiagnostics,
notifier: Notifier,
scarb_toolchain: ScarbToolchain,
) {
for file in batch {
refresh_file_diagnostics(
db,
config,
config_registry,
file,
&project_diagnostics,
¬ifier,
&scarb_toolchain,
);
}
}
#[tracing::instrument(skip_all, fields(url = tracing_file_url(db, root_on_disk_file)))]
fn refresh_file_diagnostics<'db>(
db: &'db AnalysisDatabase,
config: &Config,
config_registry: &ConfigsRegistry,
root_on_disk_file: FileId<'db>,
project_diagnostics: &ProjectDiagnostics,
notifier: &Notifier,
scarb_toolchain: &ScarbToolchain,
) {
let Some(new_files_diagnostics) =
FilesDiagnostics::collect(db, config, config_registry, scarb_toolchain, root_on_disk_file)
else {
return;
};
let (root_on_disk_file_url, new_diags) =
new_files_diagnostics.to_lsp(db, config.trace_macro_diagnostics);
let new_diags = new_diags
.into_iter()
.filter_map(|((url, file_id), mut diagnostics)| {
let is_dependency = originating_file_path(db, file_id)
.is_some_and(|p| scarb_toolchain.is_from_scarb_cache(&p));
if is_dependency {
diagnostics.retain(|diag| diag.severity == Some(DiagnosticSeverity::ERROR));
if diagnostics.is_empty() {
return None;
}
}
Some((url, diagnostics))
})
.collect();
let diags_to_send = project_diagnostics.update(root_on_disk_file_url, new_diags);
for (url, diagnostics) in diags_to_send {
notifier.notify::<PublishDiagnostics>(PublishDiagnosticsParams {
uri: url,
diagnostics,
version: None,
});
}
}
fn originating_file_path<'db>(db: &'db dyn FilesGroup, file_id: FileId<'db>) -> Option<PathBuf> {
match file_id.long(db) {
FileLongId::OnDisk(path) => Some(path.clone()),
FileLongId::Virtual(vf) => originating_file_path(db, vf.parent?.file_id),
FileLongId::External(id) => {
originating_file_path(db, ext_as_virtual(db, *id).parent?.file_id)
}
}
}
#[tracing::instrument(skip_all)]
pub fn clear_old_diagnostics(
files_to_preserve: HashSet<Url>,
project_diagnostics: ProjectDiagnostics,
notifier: Notifier,
) {
let removed = project_diagnostics.clear_old(&files_to_preserve);
for url in removed {
let params = PublishDiagnosticsParams { uri: url, diagnostics: vec![], version: None };
notifier.notify::<PublishDiagnostics>(params);
}
}
fn tracing_file_url<'db>(db: &'db AnalysisDatabase, file: FileId<'db>) -> String {
db.url_for_file(file).map(|u| u.to_string()).unwrap_or_default()
}