riscv_analysis_lsp 0.1.0-alpha

RISC-V static analysis tool -- LSP helpers
Documentation
// Type conversions for LSP

use std::collections::HashMap;
use std::iter::Peekable;

use lsp_types::{
    Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, Location, Position, Range,
};
use riscv_analysis::parser::{CanGetURIString, Lexer, RVDocument, RVParser, Range as MyRange};
use riscv_analysis::passes::DiagnosticItem;
use riscv_analysis::passes::WarningLevel;
use riscv_analysis::reader::{FileReader, FileReaderError};

mod completion;
pub use completion::*;
use serde::{Deserialize, Serialize};
use url::Url;
use uuid::Uuid;

trait RangeInto {
    fn to_range(&self) -> Range;
}

impl RangeInto for MyRange {
    fn to_range(&self) -> Range {
        lsp_types::Range {
            start: Position {
                line: self.start.line.try_into().unwrap_or(0),
                character: self.start.column.try_into().unwrap_or(0),
            },
            end: Position {
                line: self.end.line.try_into().unwrap_or(0),
                character: self.end.column.try_into().unwrap_or(0),
            },
        }
    }
}

trait WarningInto {
    fn to_severity(&self) -> DiagnosticSeverity;
}

impl WarningInto for WarningLevel {
    fn to_severity(&self) -> DiagnosticSeverity {
        match self {
            WarningLevel::Warning => DiagnosticSeverity::WARNING,
            WarningLevel::Error => DiagnosticSeverity::ERROR,
        }
    }
}

pub trait LSPDiag {
    fn to_lsp_diag(&self, parser: &RVParser<LSPFileReader>) -> LSPRVSingleDiagnostic;
}

impl LSPDiag for DiagnosticItem {
    fn to_lsp_diag(&self, parser: &RVParser<LSPFileReader>) -> LSPRVSingleDiagnostic {
        LSPRVSingleDiagnostic {
            uri: parser
                .reader
                .get_filename(self.file)
                .unwrap_or(String::new()),
            diagnostic: Diagnostic {
                range: self.range.to_range(),
                severity: Some(self.level.clone().to_severity()),
                code: None,
                code_description: None,
                source: None,
                message: self.long_description.clone(),
                related_information: self.related.clone().map(|f| {
                    f.into_iter()
                        .map(|f1| DiagnosticRelatedInformation {
                            location: Location {
                                uri: Url::parse(
                                    &parser.reader.get_filename(f1.file).unwrap_or(String::new()),
                                )
                                .unwrap(),
                                range: f1.range.to_range(),
                            },
                            message: f1.description,
                        })
                        .collect::<Vec<_>>()
                }),
                tags: None,
                data: None,
            },
        }
    }
}

#[derive(Clone)]
pub struct LSPFileReader {
    pub file_uris: HashMap<Uuid, RVDocument>,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct LSPRVDiagnostic {
    pub uri: String,
    pub diagnostics: Vec<Diagnostic>,
}

pub struct LSPRVSingleDiagnostic {
    pub uri: String,
    pub diagnostic: Diagnostic,
}

impl CanGetURIString for LSPFileReader {
    fn get_uri_string(&self, uuid: Uuid) -> RVDocument {
        self.file_uris.get(&uuid).unwrap().clone()
    }
}

impl FileReader for LSPFileReader {
    fn get_filename(&self, uuid: uuid::Uuid) -> Option<String> {
        self.file_uris.get(&uuid).map(|x| x.uri.clone())
    }

    fn import_file(
        &mut self,
        path: &str,
        in_file: Option<uuid::Uuid>,
    ) -> Result<(Uuid, Peekable<Lexer>), FileReaderError> {
        // if there is an in_file, find its path and use that as the parent
        let fulluri = match in_file {
            Some(uuid) => {
                let doc = self.file_uris.get(&uuid).unwrap();
                let uri = lsp_types::Url::parse(&doc.uri).unwrap();
                let fileuri = uri.join(path).unwrap();
                fileuri.to_string()
            }
            // otherwise, this is the full path to the file, denoted by its uri
            None => lsp_types::Url::parse(path).unwrap().to_string(),
        };

        // find file in values of hashmap
        let doc = self
            .file_uris
            .clone()
            .into_iter()
            .find(|x| x.1.uri == fulluri);

        // if file not found, return error
        if doc.is_none() {
            return Err(FileReaderError::InternalFileNotFound);
        }

        // if file found, return lexer
        let doc = doc.unwrap();
        let lexer = Lexer::new(&doc.1.text, doc.0);
        Ok((doc.0, lexer.peekable()))
    }
}

impl LSPFileReader {
    pub fn new(docs: Vec<RVDocument>) -> Self {
        let mut map = HashMap::new();

        for doc in docs {
            let uuid = Uuid::new_v4();
            map.insert(uuid, doc);
        }

        LSPFileReader { file_uris: map }
    }
}