fichu 0.1.16

A formatter for SPARQL queries
Documentation
use streaming_iterator::StreamingIterator;

use std::collections::HashSet;

use log::{error, info};

use tree_sitter::{Node, Query, QueryCursor};

use super::{
    lsp::textdocument::{Position, Range},
    state::ServerState,
    Server,
};

fn collect_all_unique_captures(node: Node, query_str: &str, text: &String) -> Vec<String> {
    match Query::new(&tree_sitter_sparql::LANGUAGE.into(), query_str) {
        Ok(query) => {
            let mut capture_set: HashSet<String> = HashSet::new();
            let mut query_cursor = QueryCursor::new();
            let mut captures = query_cursor.captures(&query, node, text.as_bytes());
            while let Some((mat, capture_index)) = captures.next() {
                let node: Node = mat.captures[*capture_index].node;
                if node.end_byte() != node.start_byte() {
                    capture_set.insert(node.utf8_text(text.as_bytes()).unwrap().to_string());
                }
            }
            capture_set.into_iter().collect()
        }

        Err(_) => {
            error!("Building a tree-sitter query failed: {}", query_str);
            vec![]
        }
    }
}

pub fn get_all_variables(analyis_state: &ServerState, uri: &String) -> Vec<String> {
    match analyis_state.get_state(uri) {
        Some((document, Some(tree))) => {
            collect_all_unique_captures(tree.root_node(), "(VAR) @variable", &document.text)
        }
        Some((_document, None)) => {
            info!("Could not compute variables for {}: No tree availible", uri);
            vec![]
        }
        None => {
            error!("Could not compute variables for {}: No such document", uri);
            vec![]
        }
    }
}

pub fn get_kind_at_position(
    analyis_state: &ServerState,
    uri: &String,
    position: &Position,
) -> Option<&'static str> {
    match analyis_state.get_tree(uri) {
        Some(tree) => {
            let point = position.to_point();
            Some(
                tree.root_node()
                    .descendant_for_point_range(point, point)?
                    .kind(),
            )
        }
        None => None,
    }
}

pub fn namespace_is_declared(
    server_state: &ServerState,
    document_uri: &String,
    namespace: &str,
) -> bool {
    let declared_namespaces = get_declared_namespaces(server_state, document_uri);
    let set: HashSet<String> = HashSet::from_iter(
        declared_namespaces
            .into_iter()
            .map(|(namespace, _range)| namespace),
    );
    set.contains(&(namespace.to_string() + ":"))
}

pub fn get_all_uncompressed_uris(server: &Server, document_uri: &String) -> Vec<(String, Range)> {
    match server.state.get_state(document_uri) {
        Some((document, Some(tree))) => {
            let declared_uris = collect_all_unique_captures(
                tree.root_node(),
                "(PrefixDecl (IRIREF) @variable)",
                &document.text,
            );
            let prefix_set: HashSet<String> = HashSet::from_iter(declared_uris.into_iter());
            let all_uris = get_all_uris(&server.state, document_uri);
            all_uris
                .into_iter()
                .filter(|(uri, _range)| !prefix_set.contains(uri))
                .collect()
        }
        _ => {
            vec![]
        }
    }
}

fn get_all_uris(analyis_state: &ServerState, document_uri: &String) -> Vec<(String, Range)> {
    match analyis_state.get_state(document_uri) {
        Some((document, Some(tree))) => {
            match Query::new(&tree_sitter_sparql::LANGUAGE.into(), "(IRIREF) @iri") {
                Ok(query) => {
                    let mut query_cursor = QueryCursor::new();
                    let mut captures =
                        query_cursor.captures(&query, tree.root_node(), document.text.as_bytes());
                    let mut namespaces: Vec<(String, Range)> = Vec::new();
                    while let Some((mat, capture_index)) = captures.next() {
                        let node = mat.captures[*capture_index].node;
                        namespaces.push((
                            node.utf8_text(document.text.as_bytes())
                                .unwrap()
                                .to_string(),
                            Range::from_node(node),
                        ));
                    }
                    return namespaces;
                }
                Err(_) => {
                    error!(
                        "Could not compute declared namspaces for {}: Query could not be build",
                        document_uri
                    );
                    vec![]
                }
            }
        }
        Some((_document, None)) => {
            info!(
                "Could not compute declared namespaces for {}: No tree availible",
                document_uri
            );
            vec![]
        }
        None => {
            error!(
                "Could not compute declared namspaces for {}: No such document",
                document_uri
            );
            vec![]
        }
    }
}

pub fn get_declared_namespaces(
    analyis_state: &ServerState,
    document_uri: &String,
) -> Vec<(String, Range)> {
    match analyis_state.get_state(document_uri) {
        Some((document, Some(tree))) => {
            match Query::new(
                &tree_sitter_sparql::LANGUAGE.into(),
                "(PrefixDecl (PNAME_NS) @namespace)",
            ) {
                Ok(query) => {
                    let mut query_cursor = QueryCursor::new();
                    let mut captures =
                        query_cursor.captures(&query, tree.root_node(), document.text.as_bytes());
                    let mut namespaces: Vec<(String, Range)> = Vec::new();
                    while let Some((mat, capture_index)) = captures.next() {
                        let node = mat.captures[*capture_index].node;
                        namespaces.push((
                            node.utf8_text(document.text.as_bytes())
                                .unwrap()
                                .to_string(),
                            Range::from_node(node),
                        ));
                    }
                    return namespaces;
                }
                Err(_) => {
                    error!(
                        "Could not compute declared namspaces for {}: Query could not be build",
                        document_uri
                    );
                    vec![]
                }
            }
        }
        Some((_document, None)) => {
            info!(
                "Could not compute declared namespaces for {}: No tree availible",
                document_uri
            );
            vec![]
        }
        None => {
            error!(
                "Could not compute declared namspaces for {}: No such document",
                document_uri
            );
            vec![]
        }
    }
}

pub fn get_used_namspaces(analyis_state: &ServerState, uri: &String) -> Vec<(String, Range)> {
    match analyis_state.get_state(uri) {
        Some((document, Some(tree))) => {
            match Query::new(
                &tree_sitter_sparql::LANGUAGE.into(),
                "(PrefixedName (PNAME_NS) @namespace)",
            ) {
                Ok(query) => {
                    let mut query_cursor = QueryCursor::new();
                    let mut captures =
                        query_cursor.captures(&query, tree.root_node(), document.text.as_bytes());
                    let mut namespaces: Vec<(String, Range)> = Vec::new();
                    while let Some((mat, capture_index)) = captures.next() {
                        let node = mat.captures[*capture_index].node;
                        namespaces.push((
                            node.utf8_text(document.text.as_bytes())
                                .unwrap()
                                .to_string(),
                            Range::from_node(node),
                        ));
                    }
                    return namespaces;
                }

                Err(_) => {
                    error!(
                        "Could not compute declared namspaces for {}: Query could not be build",
                        uri
                    );
                    vec![]
                }
            }
        }
        Some((_document, None)) => {
            info!(
                "Could not compute declared namespaces for {}: No tree availible",
                uri
            );
            vec![]
        }
        None => {
            error!(
                "Could not compute declared namspaces for {}: No such document",
                uri
            );
            vec![]
        }
    }
}

pub(crate) fn get_unused_prefixes(
    analysis_state: &ServerState,
    uri: &String,
) -> impl Iterator<Item = (String, Range)> {
    let declared_namespaces = get_declared_namespaces(analysis_state, uri);
    let declared_namespaces_set: HashSet<String> = declared_namespaces
        .iter()
        .map(|(namespace, _range)| namespace)
        .cloned()
        .collect();
    let used_namespaces = get_used_namspaces(analysis_state, uri);
    let used_namespaces_set: HashSet<String> = used_namespaces
        .iter()
        .map(|(namespace, _range)| namespace)
        .cloned()
        .collect();

    declared_namespaces
        .iter()
        .filter_map(move |(declared_namespace, range)| {
            (&declared_namespaces_set - &used_namespaces_set)
                .contains(declared_namespace)
                .then_some((declared_namespace.clone(), range.clone()))
        })
        .collect::<Vec<(String, Range)>>()
        .into_iter()
}

pub(crate) fn get_undeclared_prefixes(
    analysis_state: &ServerState,
    uri: &String,
) -> impl Iterator<Item = (String, Range)> {
    let declared_namespaces = get_declared_namespaces(analysis_state, uri);
    let declared_namespaces_set: HashSet<String> = declared_namespaces
        .iter()
        .map(|(namespace, _range)| namespace)
        .cloned()
        .collect();
    let used_namespaces = get_used_namspaces(analysis_state, uri);
    let used_namespaces_set: HashSet<String> = used_namespaces
        .iter()
        .map(|(namespace, _range)| namespace)
        .cloned()
        .collect();

    used_namespaces
        .iter()
        .filter_map(|(declared_namespace, range)| {
            (&used_namespaces_set - &declared_namespaces_set)
                .contains(declared_namespace)
                .then_some((declared_namespace.clone(), range.clone()))
        })
        .collect::<Vec<(String, Range)>>()
        .into_iter()
}

#[cfg(test)]
mod tests {
    use indoc::indoc;

    use crate::server::{
        anaysis::{
            get_declared_namespaces, get_undeclared_prefixes, get_unused_prefixes,
            get_used_namspaces,
        },
        lsp::textdocument::TextDocumentItem,
        state::ServerState,
    };

    #[test]
    fn declared_namespaces() {
        let mut state = ServerState::new();
        state.add_document(TextDocumentItem::new(
            "uri",
            indoc!(
                "PREFIX wdt: <iri>
                 PREFIX wd: <iri>
                 PREFIX wdt: <iri>

                 SELECT * {}"
            ),
        ));
        let declared_namesapces = get_declared_namespaces(&state, &"uri".to_string());
        assert_eq!(
            declared_namesapces
                .iter()
                .map(|(namespace, _range)| namespace)
                .collect::<Vec<&String>>(),
            vec!["wdt:", "wd:", "wdt:"]
        );
    }

    #[test]
    fn used_namespaces() {
        let mut state = ServerState::new();
        state.add_document(TextDocumentItem::new(
            "uri",
            indoc!("SELECT * {?a wdt:P32 ?b. ?a wd:p32 ?b. ?a wdt:P31 ?b}"),
        ));
        let declared_namesapces = get_used_namspaces(&state, &"uri".to_string());
        assert_eq!(
            declared_namesapces
                .iter()
                .map(|(namespace, _range)| namespace)
                .collect::<Vec<&String>>(),
            vec!["wdt:", "wd:", "wdt:"]
        );
    }

    #[test]
    fn undeclared_namespaces() {
        let mut state = ServerState::new();
        state.add_document(TextDocumentItem::new(
            "uri",
            indoc!("SELECT * {x:y y:p x:x}"),
        ));
        let declared_namesapces: Vec<String> = get_undeclared_prefixes(&state, &"uri".to_string())
            .map(|(namespace, _range)| namespace)
            .collect();
        assert_eq!(declared_namesapces, vec!["x:", "y:", "x:"]);
    }
    #[test]
    fn unused_namespaces() {
        let mut state = ServerState::new();
        state.add_document(TextDocumentItem::new(
            "uri",
            indoc!(
                "PREFIX wdt: <>
                 PREFIX wdt: <>

                 SELECT * {}"
            ),
        ));
        let declared_namesapces: Vec<String> = get_unused_prefixes(&state, &"uri".to_string())
            .map(|(namespace, _range)| namespace)
            .collect();
        assert_eq!(declared_namesapces, vec!["wdt:", "wdt:"]);
    }
}