use std::panic::AssertUnwindSafe;
use std::path::Path;
use lsp_types::Diagnostic;
use crate::incremental::{Analysis, normalize_path};
use crate::parser::parse;
use crate::text::PositionEncoding;
use super::format::parse_diagnostics_to_lsp;
use super::graph_diagnostics::graph_diagnostics;
use super::lint::lint_diagnostics_via_db;
pub(crate) fn document_diagnostics_via_db(
snapshot: &Analysis,
path: &Path,
text: &str,
encoding: PositionEncoding,
) -> Vec<Diagnostic> {
let cached = salsa::Cancelled::catch(AssertUnwindSafe(|| {
let file = snapshot.lookup_file(path)?;
if snapshot.file_text(file) != text {
return None;
}
Some(parse_diagnostics_to_lsp(
snapshot.parse_diagnostics(file),
text,
encoding,
))
}));
let mut diags = match cached {
Ok(Some(diags)) => diags,
Ok(None) | Err(_) => parse_diagnostics_to_lsp(&parse(text).diagnostics, text, encoding),
};
if diags.is_empty() {
diags.extend(lint_diagnostics_via_db(snapshot, path, text, encoding));
}
diags.extend(graph_diagnostics_for(snapshot, path, encoding));
diags
}
fn graph_diagnostics_for(
snapshot: &Analysis,
path: &Path,
encoding: PositionEncoding,
) -> Vec<Diagnostic> {
let computed = salsa::Cancelled::catch(AssertUnwindSafe(|| {
let graph = snapshot.project_graph();
graph_diagnostics(graph, encoding, |member| {
let file = snapshot.lookup_file(member)?;
Some((
snapshot.file_text_of(file).to_string(),
snapshot.parsed_tree(file),
))
})
.remove(&normalize_path(path))
.unwrap_or_default()
}));
computed.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::incremental::IncrementalDatabase;
use lsp_types::{DiagnosticSeverity, NumberOrString};
use std::path::PathBuf;
fn report_for(text: &str) -> Vec<Diagnostic> {
let path = PathBuf::from("/work/a.jl");
let mut db = IncrementalDatabase::default();
db.upsert_file(&path, text.to_string());
document_diagnostics_via_db(&db.snapshot(), &path, text, PositionEncoding::Utf16)
}
#[test]
fn a_clean_document_reports_nothing() {
assert_eq!(report_for("x = 1\n"), Vec::new());
}
#[test]
fn lint_findings_join_the_report() {
let diags = report_for("function f(x)\n tmp = x + 1\n return x\nend\n");
assert_eq!(diags.len(), 1);
assert_eq!(
diags[0].code,
Some(NumberOrString::String("unused-binding".to_string()))
);
}
#[test]
fn parse_errors_suppress_lint_findings() {
let diags = report_for("function f(x)\n tmp = x + 1\n return x\n");
assert!(!diags.is_empty());
assert!(
diags
.iter()
.all(|d| d.severity == Some(DiagnosticSeverity::ERROR) && d.code.is_none()),
"a parse-broken buffer must report parse errors only, got {diags:?}"
);
}
#[test]
fn falls_back_when_the_db_lags() {
let text = "function f(x)\n tmp = x + 1\n return x\nend\n";
let db = IncrementalDatabase::default();
let diags = document_diagnostics_via_db(
&db.snapshot(),
Path::new("/work/never-seen.jl"),
text,
PositionEncoding::Utf16,
);
assert_eq!(diags.len(), 1);
assert_eq!(
diags[0].code,
Some(NumberOrString::String("unused-binding".to_string()))
);
}
}