pub mod invalid_projection_variable;
pub mod same_subject;
pub mod syntax_error;
pub mod uncompacted_uri;
pub mod undeclared_prefix;
pub mod ungrouped_select_variable;
pub mod unused_prefix_declaration;
use super::code_action::remove_prefix_declaration;
use crate::server::{
Server,
lsp::{
DiagnosticRequest, DiagnosticResponse, WorkspaceEditRequest, base_types::LSPAny,
diagnostic::Diagnostic, errors::LSPError,
},
message_handler::code_action::declare_prefix,
};
use futures::lock::Mutex;
use ll_sparql_parser::ast::{AstNode, QueryUnit};
use std::{
collections::{HashMap, HashSet},
convert::identity,
rc::Rc,
};
#[tracing::instrument(skip_all, fields(id = %request.get_id(), uri = %request.params.text_document.uri))]
pub(super) async fn handle_diagnostic_request(
server_rc: Rc<Mutex<Server>>,
request: DiagnosticRequest,
) -> Result<(), LSPError> {
let mut server = server_rc.lock().await;
let document = server
.state
.get_document(&request.params.text_document.uri)?;
let tree = server
.state
.get_cached_parse_tree(&request.params.text_document.uri)?
.tree;
let ast = QueryUnit::cast(tree).ok_or(LSPError::new(
crate::server::lsp::errors::ErrorCode::InternalError,
"diagnostics are currently only supported for query operations",
))?;
let mut diagnostic_accu = Vec::new();
macro_rules! add {
($diagnostic_provider:path) => {
if let Some(diagnostics) = $diagnostic_provider(document, &ast, &server) {
diagnostic_accu.extend(diagnostics);
}
};
}
add!(unused_prefix_declaration::diagnostics);
add!(undeclared_prefix::diagnostics);
add!(uncompacted_uri::diagnostics);
add!(ungrouped_select_variable::diagnostics);
add!(invalid_projection_variable::diagnostics);
add!(same_subject::diagnostics);
add!(syntax_error::diagnostics);
if client_support_workspace_edits(&server) {
declare_and_undeclare_prefixes(&mut server, &request, &diagnostic_accu);
}
server.send_message(DiagnosticResponse::new(request.get_id(), diagnostic_accu))
}
fn declare_and_undeclare_prefixes(
server: &mut Server,
request: &DiagnosticRequest,
diagnostics: &[Diagnostic],
) {
let document_uri = request.params.text_document.uri.clone();
let mut prefixes = HashSet::<&str>::new();
let edits: Vec<_> = diagnostics
.iter()
.filter_map(|diagnostic| {
if let Some(LSPAny::String(prefix)) = diagnostic.data.as_ref() {
if prefixes.insert(prefix) {
match diagnostic.code.as_ref() {
Some(code)
if code == &*undeclared_prefix::CODE
&& server.settings.prefixes.as_ref().is_some_and(|prefixes| {
prefixes.add_missing.is_some_and(identity)
}) =>
{
declare_prefix(server, &document_uri, diagnostic.clone())
}
Some(code)
if code == &*unused_prefix_declaration::CODE
&& server.settings.prefixes.as_ref().is_some_and(|prefixes| {
prefixes.remove_unused.is_some_and(identity)
}) =>
{
remove_prefix_declaration(server, &document_uri, diagnostic.clone())
}
_ => Ok(None),
}
.ok()
.flatten()
.and_then(|code_action| code_action.edit.changes)
.and_then(|mut changes| changes.remove(&document_uri))
} else {
None
}
} else {
None
}
})
.flatten()
.collect();
if !edits.is_empty() {
let request_id = server.bump_request_id();
if let Err(err) = server.send_message(WorkspaceEditRequest::new(
request_id,
HashMap::from_iter([(document_uri, edits)]),
)) {
tracing::error!("Sending \"workspace/applyEdit\" request failed:\n{:?}", err);
}
}
}
fn client_support_workspace_edits(server: &Server) -> bool {
server
.client_capabilities
.as_ref()
.is_some_and(|client_capabilities| {
client_capabilities
.workspace
.as_ref()
.and_then(|workspace_capabilities| workspace_capabilities.apply_edit)
.is_some_and(|flag| flag)
&& client_capabilities
.workspace
.as_ref()
.and_then(|workspace_capabilities| {
workspace_capabilities.workspace_edit.as_ref()
})
.is_some_and(|capability| capability.document_changes.is_some_and(|flag| flag))
})
}