diff options
Diffstat (limited to 'support/texlab/src/diagnostics/bibtex.rs')
-rw-r--r-- | support/texlab/src/diagnostics/bibtex.rs | 320 |
1 files changed, 320 insertions, 0 deletions
diff --git a/support/texlab/src/diagnostics/bibtex.rs b/support/texlab/src/diagnostics/bibtex.rs new file mode 100644 index 0000000000..8e5b602bcd --- /dev/null +++ b/support/texlab/src/diagnostics/bibtex.rs @@ -0,0 +1,320 @@ +use crate::syntax::*; +use crate::workspace::Document; +use lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; + +#[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: &BibtexSyntaxTree) -> Vec<Self> { + let mut errors = Vec::new(); + for entry in tree.entries() { + if entry.is_comment() { + continue; + } + + if entry.left.is_none() { + errors.push(BibtexError::new( + BibtexErrorCode::MissingBeginBrace, + entry.ty.end(), + )); + continue; + } + + if entry.key.is_none() { + errors.push(BibtexError::new( + BibtexErrorCode::MissingEntryKey, + entry.left.as_ref().unwrap().end(), + )); + continue; + } + + if entry.comma.is_none() { + errors.push(BibtexError::new( + BibtexErrorCode::MissingComma, + entry.key.as_ref().unwrap().end(), + )); + continue; + } + + for i in 0..entry.fields.len() { + let field = &entry.fields[i]; + if field.assign.is_none() { + errors.push(BibtexError::new( + BibtexErrorCode::MissingAssign, + field.name.end(), + )); + continue; + } + + if field.content.is_none() { + errors.push(BibtexError::new( + BibtexErrorCode::MissingContent, + field.assign.as_ref().unwrap().end(), + )); + continue; + } + + Self::analyze_content(&mut errors, &field.content.as_ref().unwrap()); + + if i != entry.fields.len() - 1 && field.comma.is_none() { + errors.push(BibtexError::new( + BibtexErrorCode::MissingComma, + field.content.as_ref().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>, content: &BibtexContent) { + match content { + BibtexContent::QuotedContent(content) => { + for child in &content.children { + Self::analyze_content(&mut errors, &child); + } + + if content.right.is_none() { + errors.push(BibtexError::new( + BibtexErrorCode::MissingQuote, + content.end(), + )); + } + } + BibtexContent::BracedContent(content) => { + for child in &content.children { + Self::analyze_content(&mut errors, &child); + } + + if content.right.is_none() { + errors.push(BibtexError::new( + BibtexErrorCode::MissingEndBrace, + content.end(), + )); + } + } + BibtexContent::Concat(concat) => { + Self::analyze_content(&mut errors, &concat.left); + match &concat.right { + Some(right) => { + Self::analyze_content(&mut errors, right); + } + None => { + errors.push(BibtexError::new( + BibtexErrorCode::MissingContent, + concat.end(), + )); + } + } + } + BibtexContent::Word(_) | BibtexContent::Command(_) => {} + } + } +} + +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, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct BibtexDiagnosticsProvider; + +impl BibtexDiagnosticsProvider { + pub fn get(self, document: &Document) -> Vec<Diagnostic> { + if let SyntaxTree::Bibtex(tree) = &document.tree { + BibtexError::analyze(&tree) + .into_iter() + .map(Into::into) + .collect() + } else { + Vec::new() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_begin_brace() { + let errors = BibtexError::analyze(&"@article".into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingBeginBrace, + Position::new(0, 8), + )] + ); + } + + #[test] + fn test_entry_key() { + let errors = BibtexError::analyze(&"@article{".into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingEntryKey, + Position::new(0, 9), + )] + ); + } + + #[test] + fn test_entry_comma() { + let errors = BibtexError::analyze(&"@article{foo".into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingComma, + Position::new(0, 12), + )] + ); + } + + #[test] + fn test_entry_end_brace() { + let errors = BibtexError::analyze(&"@article{foo,".into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingEndBrace, + Position::new(0, 13), + )] + ); + } + + #[test] + fn test_field_equals() { + let errors = BibtexError::analyze(&"@article{foo, bar}".into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingAssign, + Position::new(0, 17), + )] + ); + } + + #[test] + fn test_field_content() { + let errors = BibtexError::analyze(&"@article{foo,\nbar = }".into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingContent, + Position::new(1, 5), + )] + ); + } + + #[test] + fn test_field_comma() { + let text = "@article{foo,\nfoo = bar\nbaz = qux}"; + let errors = BibtexError::analyze(&text.into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingComma, + Position::new(1, 9), + )] + ); + } + + #[test] + fn test_content_quote() { + let text = "@article{foo, bar =\n\"}"; + let errors = BibtexError::analyze(&text.into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingQuote, + Position::new(1, 1), + )] + ); + } + + #[test] + fn test_content_brace() { + let text = "@article{foo, bar =\n{"; + let errors = BibtexError::analyze(&text.into()); + assert_eq!( + errors, + vec![ + BibtexError::new(BibtexErrorCode::MissingEndBrace, Position::new(1, 1)), + BibtexError::new(BibtexErrorCode::MissingEndBrace, Position::new(1, 1)), + ] + ); + } + + #[test] + fn test_content_concat() { + let text = "@article{foo, bar = baz \n# }"; + let errors = BibtexError::analyze(&text.into()); + assert_eq!( + errors, + vec![BibtexError::new( + BibtexErrorCode::MissingContent, + Position::new(1, 1) + )] + ); + } + + #[test] + fn test_entry_valid() { + let text = "@article{foo, bar = \"baz {qux}\" # quux}"; + let errors = BibtexError::analyze(&text.into()); + assert_eq!(errors, Vec::new()); + } +} |