diff --git a/src/diagnostic.rs b/src/diagnostic.rs index d989cff..c12b927 100644 --- a/src/diagnostic.rs +++ b/src/diagnostic.rs @@ -453,7 +453,7 @@ impl AnalyzerModule for LexicalAnalyzer { } impl LexicalAnalyzer { - fn lex_error_to_diagnostic(error: &LexError, source: &str) -> Diagnostic { + pub fn lex_error_to_diagnostic(error: &LexError, source: &str) -> Diagnostic { match error { LexError::InvalidToken { position, text } => { let pos = Position::from_text_size(TextSize::from(*position as u32), source); @@ -478,27 +478,17 @@ impl LexicalAnalyzer { "Unterminated string literal".to_string(), ) } - LexError::InvalidNumber { text } => { - if let Some(pos) = source.find(text) { - let start_pos = Position::from_text_size(TextSize::from(pos as u32), source); - let end_pos = - Position::new(start_pos.line, start_pos.character + text.len() as u32); - let range = Range::new(start_pos, end_pos); - Diagnostic::new( - range, - DiagnosticSeverity::Error, - DiagnosticCode::InvalidNumber, - format!("Invalid number: '{}'", text), - ) - } else { - let range = Range::single_position(Position::new(0, 0)); - Diagnostic::new( - range, - DiagnosticSeverity::Error, - DiagnosticCode::InvalidNumber, - format!("Invalid number: '{}'", text), - ) - } + LexError::InvalidNumber { position, text } => { + let start_pos = Position::from_text_size(TextSize::from(*position as u32), source); + let end_pos = + Position::new(start_pos.line, start_pos.character + text.len() as u32); + let range = Range::new(start_pos, end_pos); + Diagnostic::new( + range, + DiagnosticSeverity::Error, + DiagnosticCode::InvalidNumber, + format!("Invalid number: '{}'", text), + ) } } } diff --git a/src/lexer.rs b/src/lexer.rs index 4cd0505..bb342dd 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -10,8 +10,8 @@ pub enum LexError { InvalidToken { position: usize, text: String }, #[error("Unterminated string literal starting at position {position}")] UnterminatedString { position: usize }, - #[error("Invalid numeric literal: {text}")] - InvalidNumber { text: String }, + #[error("Invalid numeric literal at position {position}: {text}")] + InvalidNumber { position: usize, text: String }, } /// Result type for lexical analysis @@ -356,6 +356,7 @@ impl<'a> NftablesLexer<'a> { .any(|c| !c.is_ascii_digit() && c != '.' && c != 'x' && c != 'X') { return Err(LexError::InvalidNumber { + position: span.start, text: text.to_owned(), }); } else { @@ -448,4 +449,38 @@ mod tests { panic!("Expected InvalidToken error"); } } + + #[test] + fn test_invalid_number_with_position() { + // Test that we can create a proper diagnostic with position information + use crate::diagnostic::LexicalAnalyzer; + + // Create a source with the same invalid pattern at different positions + let source = "123abc normal 123abc end"; + + // Since normal tokenization splits "123abc" into "123" + "abc", + // let's test the diagnostic creation directly with a mock error + let error1 = LexError::InvalidNumber { + position: 0, + text: "123abc".to_string(), + }; + let error2 = LexError::InvalidNumber { + position: 14, + text: "123abc".to_string(), + }; + + // Test that diagnostics are created with correct positions + let diagnostic1 = LexicalAnalyzer::lex_error_to_diagnostic(&error1, source); + let diagnostic2 = LexicalAnalyzer::lex_error_to_diagnostic(&error2, source); + + // First occurrence should be at position 0 + assert_eq!(diagnostic1.range.start.line, 0); + assert_eq!(diagnostic1.range.start.character, 0); + assert_eq!(diagnostic1.message, "Invalid number: '123abc'"); + + // Second occurrence should be at position 14 (not 0) + assert_eq!(diagnostic2.range.start.line, 0); + assert_eq!(diagnostic2.range.start.character, 14); + assert_eq!(diagnostic2.message, "Invalid number: '123abc'"); + } }