diff options
Diffstat (limited to 'support/texlab/src/diagnostics/bibtex.rs')
-rw-r--r-- | support/texlab/src/diagnostics/bibtex.rs | 450 |
1 files changed, 128 insertions, 322 deletions
diff --git a/support/texlab/src/diagnostics/bibtex.rs b/support/texlab/src/diagnostics/bibtex.rs index 80cca80319..efea141db0 100644 --- a/support/texlab/src/diagnostics/bibtex.rs +++ b/support/texlab/src/diagnostics/bibtex.rs @@ -1,341 +1,147 @@ -use crate::{ - protocol::{Diagnostic, DiagnosticSeverity, Position, Range}, - syntax::{bibtex, SyntaxNode}, - workspace::{Document, DocumentContent}, -}; -use petgraph::graph::NodeIndex; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum BibtexErrorCode { - MissingBeginBrace, - MissingEntryKey, - MissingComma, - MissingEndBrace, - MissingAssign, - MissingContent, - MissingQuote, -} - -impl BibtexErrorCode { - pub fn message(self) -> &'static str { - match self { - BibtexErrorCode::MissingBeginBrace => "Expecting a curly bracket: \"{\"", - BibtexErrorCode::MissingEntryKey => "Expecting an entry key", - BibtexErrorCode::MissingComma => "Expecting a comma: \",\"", - BibtexErrorCode::MissingEndBrace => "Expecting a curly bracket: \"}\"", - BibtexErrorCode::MissingAssign => "Expecting an equals sign: \"=\"", - BibtexErrorCode::MissingContent => "Expecting content", - BibtexErrorCode::MissingQuote => "Expecting a quote: '\"'", - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct BibtexError { - code: BibtexErrorCode, - position: Position, -} - -impl BibtexError { - pub fn new(code: BibtexErrorCode, position: Position) -> Self { - Self { code, position } - } - - pub fn analyze(tree: &bibtex::Tree) -> Vec<Self> { - let mut errors = Vec::new(); - for entry_node in tree.children(tree.root) { - if let Some(entry) = tree.as_entry(entry_node) { - if entry.is_comment() { - continue; - } - - if entry.left.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingBeginBrace, - entry.ty.end(), - )); - continue; - } +use std::sync::Arc; - if entry.key.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingEntryKey, - entry.left.as_ref().unwrap().end(), - )); - continue; - } +use cstree::TextRange; +use lsp_types::{Diagnostic, DiagnosticSeverity}; +use multimap::MultiMap; - if entry.comma.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingComma, - entry.key.as_ref().unwrap().end(), - )); - continue; - } - - let field_count = tree.children(entry_node).count(); - for (i, field_node) in tree.children(entry_node).enumerate() { - let field = tree.as_field(field_node).unwrap(); - if field.assign.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingAssign, - field.name.end(), - )); - continue; - } - - let content = tree.children(field_node).next(); - - if content.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingContent, - field.assign.as_ref().unwrap().end(), - )); - continue; - } - - Self::analyze_content(&mut errors, tree, content.unwrap()); - - if i != field_count - 1 && field.comma.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingComma, - tree.graph[content.unwrap()].end(), - )); - continue; - } - } - - if entry.right.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingEndBrace, - entry.end(), - )); - continue; - } - } - } - errors - } - - fn analyze_content( - mut errors: &mut Vec<BibtexError>, - tree: &bibtex::Tree, - content_node: NodeIndex, - ) { - match &tree.graph[content_node] { - bibtex::Node::QuotedContent(content) => { - for child in tree.children(content_node) { - Self::analyze_content(&mut errors, tree, child); - } - - if content.right.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingQuote, - content.end(), - )); - } - } - bibtex::Node::BracedContent(content) => { - for child in tree.children(content_node) { - Self::analyze_content(&mut errors, tree, child); - } +use crate::{ + syntax::{ + bibtex::{self, HasDelimiters, HasType}, + CstNode, + }, + Document, LineIndexExt, Uri, Workspace, +}; - if content.right.is_none() { - errors.push(BibtexError::new( - BibtexErrorCode::MissingEndBrace, - content.end(), - )); - } - } - bibtex::Node::Concat(concat) => { - let mut children = tree.children(content_node); - Self::analyze_content(&mut errors, tree, children.next().unwrap()); - match children.next() { - Some(right) => { - Self::analyze_content(&mut errors, tree, right); - } - None => { - errors.push(BibtexError::new( - BibtexErrorCode::MissingContent, - concat.end(), - )); - } - } - } - bibtex::Node::Root(_) - | bibtex::Node::Comment(_) - | bibtex::Node::Preamble(_) - | bibtex::Node::String(_) - | bibtex::Node::Entry(_) - | bibtex::Node::Field(_) - | bibtex::Node::Word(_) - | bibtex::Node::Command(_) => {} - } - } -} +pub fn analyze_bibtex_static( + workspace: &dyn Workspace, + diagnostics_by_uri: &mut MultiMap<Arc<Uri>, Diagnostic>, + uri: &Uri, +) -> Option<()> { + let document = workspace.get(uri)?; + let data = document.data.as_bibtex()?; -impl Into<Diagnostic> for BibtexError { - fn into(self) -> Diagnostic { - Diagnostic { - source: Some("bibtex".into()), - range: Range::new(self.position, self.position), - message: self.code.message().into(), - severity: Some(DiagnosticSeverity::Error), - code: None, - related_information: None, - tags: None, - } + for node in data.root.descendants() { + analyze_entry(&document, diagnostics_by_uri, node) + .or_else(|| analyze_field(&document, diagnostics_by_uri, node)); } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct BibtexDiagnosticsProvider; -impl BibtexDiagnosticsProvider { - pub fn get(self, doc: &Document) -> Vec<Diagnostic> { - if let DocumentContent::Bibtex(tree) = &doc.content { - BibtexError::analyze(&tree) - .into_iter() - .map(Into::into) - .collect() - } else { - Vec::new() - } - } + Some(()) } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn begin_brace() { - let errors = BibtexError::analyze(&bibtex::open("@article")); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingBeginBrace, - Position::new(0, 8), - )] - ); - } - - #[test] - fn entry_key() { - let errors = BibtexError::analyze(&bibtex::open("@article{")); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingEntryKey, - Position::new(0, 9), - )] - ); - } - - #[test] - fn entry_comma() { - let errors = BibtexError::analyze(&bibtex::open("@article{foo")); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingComma, - Position::new(0, 12), - )] - ); - } - - #[test] - fn entry_end_brace() { - let errors = BibtexError::analyze(&bibtex::open("@article{foo,")); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingEndBrace, - Position::new(0, 13), - )] +fn analyze_entry( + document: &Document, + diagnostics_by_uri: &mut MultiMap<Arc<Uri>, Diagnostic>, + node: &bibtex::SyntaxNode, +) -> Option<()> { + let entry = bibtex::Entry::cast(node)?; + if entry.left_delimiter().is_none() { + diagnostics_by_uri.insert( + Arc::clone(&document.uri), + Diagnostic { + range: document + .line_index + .line_col_lsp_range(entry.ty()?.text_range()), + severity: Some(DiagnosticSeverity::Error), + code: None, + code_description: None, + source: Some("texlab".to_string()), + message: "Expecting a curly bracket: \"{\"".to_string(), + related_information: None, + tags: None, + data: None, + }, ); - } - - #[test] - fn field_equals() { - let errors = BibtexError::analyze(&bibtex::open("@article{foo, bar}")); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingAssign, - Position::new(0, 17), - )] + return Some(()); + } + + if entry.key().is_none() { + diagnostics_by_uri.insert( + Arc::clone(&document.uri), + Diagnostic { + range: document + .line_index + .line_col_lsp_range(entry.left_delimiter()?.text_range()), + severity: Some(DiagnosticSeverity::Error), + code: None, + code_description: None, + source: Some("texlab".to_string()), + message: "Expecting a key".to_string(), + related_information: None, + tags: None, + data: None, + }, ); - } - - #[test] - fn field_content() { - let errors = BibtexError::analyze(&bibtex::open("@article{foo,\nbar = }")); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingContent, - Position::new(1, 5), - )] - ); - } - - #[test] - fn field_comma() { - let text = "@article{foo,\nfoo = bar\nbaz = qux}"; - let errors = BibtexError::analyze(&bibtex::open(text)); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingComma, - Position::new(1, 9), - )] + return Some(()); + } + + if entry.key().is_none() { + diagnostics_by_uri.insert( + Arc::clone(&document.uri), + Diagnostic { + range: document + .line_index + .line_col_lsp_range(entry.right_delimiter()?.text_range()), + severity: Some(DiagnosticSeverity::Error), + code: None, + code_description: None, + source: Some("texlab".to_string()), + message: "Expecting a curly bracket: \"}\"".to_string(), + related_information: None, + tags: None, + data: None, + }, ); + return Some(()); } - #[test] - fn content_quote() { - let text = "@article{foo, bar =\n\"}"; - let errors = BibtexError::analyze(&bibtex::open(text)); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingQuote, - Position::new(1, 1), - )] - ); - } + Some(()) +} - #[test] - fn content_brace() { - let text = "@article{foo, bar =\n{"; - let errors = BibtexError::analyze(&bibtex::open(text)); - assert_eq!( - errors, - vec![ - BibtexError::new(BibtexErrorCode::MissingEndBrace, Position::new(1, 1)), - BibtexError::new(BibtexErrorCode::MissingEndBrace, Position::new(1, 1)), - ] +fn analyze_field( + document: &Document, + diagnostics_by_uri: &mut MultiMap<Arc<Uri>, Diagnostic>, + node: &bibtex::SyntaxNode, +) -> Option<()> { + let field = bibtex::Field::cast(node)?; + if field.equality_sign().is_none() { + diagnostics_by_uri.insert( + Arc::clone(&document.uri), + Diagnostic { + range: document + .line_index + .line_col_lsp_range(TextRange::empty(field.name()?.text_range().end())), + severity: Some(DiagnosticSeverity::Error), + code: None, + code_description: None, + source: Some("texlab".to_string()), + message: "Expecting an equality sign: \"=\"".to_string(), + related_information: None, + tags: None, + data: None, + }, ); - } - - #[test] - fn content_concat() { - let text = "@article{foo, bar = baz \n# }"; - let errors = BibtexError::analyze(&bibtex::open(text)); - assert_eq!( - errors, - vec![BibtexError::new( - BibtexErrorCode::MissingContent, - Position::new(1, 1) - )] + return Some(()); + } + + if field.value().is_none() { + diagnostics_by_uri.insert( + Arc::clone(&document.uri), + Diagnostic { + range: document.line_index.line_col_lsp_range(TextRange::empty( + field.equality_sign()?.text_range().end(), + )), + severity: Some(DiagnosticSeverity::Error), + code: None, + code_description: None, + source: Some("texlab".to_string()), + message: "Expecting a field value".to_string(), + related_information: None, + tags: None, + data: None, + }, ); + return Some(()); } - #[test] - fn entry_valid() { - let text = "@article{foo, bar = \"baz {qux}\" # quux}"; - let errors = BibtexError::analyze(&bibtex::open(text)); - assert_eq!(errors, Vec::new()); - } + Some(()) } |