feat: Integrate parser into diagnostics system for meaningful error reporting

This commit is contained in:
Josh Thomas 2025-01-07 13:10:56 -06:00
parent 9c268e80f7
commit 9b8a912d9f

View file

@ -1,5 +1,8 @@
use tower_lsp::lsp_types::*;
use crate::documents::TextDocument;
use djls_template_ast::{Lexer, Parser};
use djls_template_ast::ast::{Ast, Node, LineOffsets, Span};
use djls_template_ast::tokens::TokenStream;
pub struct Diagnostics;
@ -7,29 +10,82 @@ impl Diagnostics {
pub fn generate_for_document(document: &TextDocument) -> Vec<Diagnostic> {
let mut diagnostics = Vec::new();
// Simple example: Check for TODO comments
for (line_num, line) in document.get_text().lines().enumerate() {
if let Some(col) = line.find("TODO") {
let text = document.get_text();
let lexer = Lexer::new(text);
let token_stream = match lexer.tokenize() {
Ok(tokens) => tokens,
Err(e) => {
diagnostics.push(Diagnostic {
range: Range {
start: Position {
line: line_num as u32,
character: col as u32
},
end: Position {
line: line_num as u32,
character: (col + 4) as u32
},
},
severity: Some(DiagnosticSeverity::INFORMATION),
code: Some(NumberOrString::String("django.todo".to_string())),
range: Range::new(Position::new(0, 0), Position::new(0, 0)),
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::String("lexing_error".to_string())),
source: Some("Django LSP".to_string()),
message: "Found TODO comment".to_string(),
message: format!("Lexing error: {}", e),
..Default::default()
});
return diagnostics;
}
};
let mut parser = Parser::new(token_stream);
let (ast, parse_errors) = match parser.parse() {
Ok(result) => result,
Err(e) => {
diagnostics.push(Diagnostic {
range: Range::new(Position::new(0, 0), Position::new(0, 0)),
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::String("parsing_error".to_string())),
source: Some("Django LSP".to_string()),
message: format!("Parsing error: {}", e),
..Default::default()
});
return diagnostics;
}
};
for error in parse_errors {
diagnostics.push(Diagnostic {
range: Range::new(Position::new(0, 0), Position::new(0, 0)), // Adjust range based on error
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::String("parse_error".to_string())),
source: Some("Django LSP".to_string()),
message: format!("Parse error: {}", error),
..Default::default()
});
}
for node in ast.nodes() {
match node {
Node::Block(block) => {
if let Some(closing) = block.closing() {
} else {
let span = block.tag().span;
let range = get_range_from_span(&ast.line_offsets(), &span);
diagnostics.push(Diagnostic {
range,
severity: Some(DiagnosticSeverity::ERROR),
code: Some(NumberOrString::String("unclosed_block".to_string())),
source: Some("Django LSP".to_string()),
message: format!("Unclosed block tag '{}'", block.tag().name),
..Default::default()
});
}
},
Node::Variable { .. } => {},
_ => {},
}
}
diagnostics
}
}
fn get_range_from_span(line_offsets: &LineOffsets, span: &Span) -> Range {
let (start_line, start_col) = line_offsets.position_to_line_col(span.start as usize);
let (end_line, end_col) = line_offsets.position_to_line_col((span.start + span.length) as usize);
Range {
start: Position::new(start_line as u32 - 1, start_col as u32),
end: Position::new(end_line as u32 - 1, end_col as u32),
}
}