mirror of
https://github.com/joshuadavidthomas/django-language-server.git
synced 2025-09-10 12:26:50 +00:00
checkpoint
This commit is contained in:
parent
55b2b92fc1
commit
760195568b
7 changed files with 161 additions and 6 deletions
|
@ -47,9 +47,9 @@ impl LineOffsets {
|
||||||
pub fn position_to_line_col(&self, position: usize) -> (usize, usize) {
|
pub fn position_to_line_col(&self, position: usize) -> (usize, usize) {
|
||||||
let position = position as u32;
|
let position = position as u32;
|
||||||
let line = match self.0.binary_search(&position) {
|
let line = match self.0.binary_search(&position) {
|
||||||
Ok(exact_line) => exact_line, // Position is at start of this line
|
Ok(exact_line) => exact_line, // Position is at start of this line
|
||||||
Err(0) => 0, // Before first line start
|
Err(0) => 0, // Before first line start
|
||||||
Err(next_line) => next_line - 1, // We're on the previous line
|
Err(next_line) => next_line - 1, // We're on the previous line
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate column as offset from line start
|
// Calculate column as offset from line start
|
||||||
|
@ -284,7 +284,9 @@ mod tests {
|
||||||
|
|
||||||
if let Node::Variable { span, .. } = var_node {
|
if let Node::Variable { span, .. } = var_node {
|
||||||
// Variable starts after newline + "{{"
|
// Variable starts after newline + "{{"
|
||||||
let (line, col) = ast.line_offsets().position_to_line_col(*span.start() as usize);
|
let (line, col) = ast
|
||||||
|
.line_offsets()
|
||||||
|
.position_to_line_col(*span.start() as usize);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(line, col),
|
(line, col),
|
||||||
(2, 3),
|
(2, 3),
|
||||||
|
@ -349,11 +351,11 @@ mod tests {
|
||||||
eprintln!("Nodes: {:?}", nodes);
|
eprintln!("Nodes: {:?}", nodes);
|
||||||
assert_eq!(nodes.len(), 1);
|
assert_eq!(nodes.len(), 1);
|
||||||
if let Node::Text { content, span } = &nodes[0] {
|
if let Node::Text { content, span } = &nodes[0] {
|
||||||
assert_eq!(content, " Welcome!");
|
assert_eq!(content, "Welcome!");
|
||||||
eprintln!("Line offsets: {:?}", ast.line_offsets());
|
eprintln!("Line offsets: {:?}", ast.line_offsets());
|
||||||
eprintln!("Span: {:?}", span);
|
eprintln!("Span: {:?}", span);
|
||||||
let (line, col) = ast.line_offsets().position_to_line_col(span.start as usize);
|
let (line, col) = ast.line_offsets().position_to_line_col(span.start as usize);
|
||||||
assert_eq!((line, col), (2, 0), "Content should be on line 2, col 0");
|
assert_eq!((line, col), (2, 2), "Content should be on line 2, col 2");
|
||||||
|
|
||||||
// Check closing tag
|
// Check closing tag
|
||||||
if let Block::Closing { tag } =
|
if let Block::Closing { tag } =
|
||||||
|
|
|
@ -303,6 +303,20 @@ impl Parser {
|
||||||
| TokenType::Comment(_, _, _)
|
| TokenType::Comment(_, _, _)
|
||||||
| TokenType::Newline
|
| TokenType::Newline
|
||||||
| TokenType::Eof => break,
|
| TokenType::Eof => break,
|
||||||
|
TokenType::Whitespace(_) => {
|
||||||
|
// Check if next token is a newline
|
||||||
|
if let Ok(next) = self.peek_at(1) {
|
||||||
|
if matches!(next.token_type(), TokenType::Newline) {
|
||||||
|
self.consume()?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Not before newline, treat as normal text
|
||||||
|
let token_text = token.token_type().to_string();
|
||||||
|
text.push_str(&token_text);
|
||||||
|
total_length += u32::try_from(token_text.len()).unwrap();
|
||||||
|
self.consume()?;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let token_text = token.token_type().to_string();
|
let token_text = token.token_type().to_string();
|
||||||
text.push_str(&token_text);
|
text.push_str(&token_text);
|
||||||
|
@ -428,6 +442,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_html_tag() {
|
fn test_parse_html_tag() {
|
||||||
let source = "<div class=\"container\">Hello</div>";
|
let source = "<div class=\"container\">Hello</div>";
|
||||||
|
@ -437,6 +452,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_html_void() {
|
fn test_parse_html_void() {
|
||||||
let source = "<input type=\"text\" />";
|
let source = "<input type=\"text\" />";
|
||||||
|
@ -447,6 +463,7 @@ mod tests {
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod django {
|
mod django {
|
||||||
use super::*;
|
use super::*;
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -458,6 +475,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_filter_chains() {
|
fn test_parse_filter_chains() {
|
||||||
let source = "{{ value|default:'nothing'|title|upper }}";
|
let source = "{{ value|default:'nothing'|title|upper }}";
|
||||||
|
@ -467,6 +485,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_django_if_block() {
|
fn test_parse_django_if_block() {
|
||||||
let source = "{% if user.is_authenticated %}Welcome{% endif %}";
|
let source = "{% if user.is_authenticated %}Welcome{% endif %}";
|
||||||
|
@ -476,6 +495,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_django_for_block() {
|
fn test_parse_django_for_block() {
|
||||||
let source = "{% for item in items %}{{ item }}{% empty %}No items{% endfor %}";
|
let source = "{% for item in items %}{{ item }}{% empty %}No items{% endfor %}";
|
||||||
|
@ -485,6 +505,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_complex_if_elif() {
|
fn test_parse_complex_if_elif() {
|
||||||
let source = "{% if x > 0 %}Positive{% elif x < 0 %}Negative{% else %}Zero{% endif %}";
|
let source = "{% if x > 0 %}Positive{% elif x < 0 %}Negative{% else %}Zero{% endif %}";
|
||||||
|
@ -494,6 +515,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_nested_for_if() {
|
fn test_parse_nested_for_if() {
|
||||||
let source =
|
let source =
|
||||||
|
@ -504,6 +526,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_mixed_content() {
|
fn test_parse_mixed_content() {
|
||||||
let source = "Welcome, {% if user.is_authenticated %}
|
let source = "Welcome, {% if user.is_authenticated %}
|
||||||
|
@ -526,8 +549,10 @@ mod tests {
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod script {
|
mod script {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_script() {
|
fn test_parse_script() {
|
||||||
let source = r#"<script type="text/javascript">
|
let source = r#"<script type="text/javascript">
|
||||||
|
@ -544,8 +569,10 @@ mod tests {
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod style {
|
mod style {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_style() {
|
fn test_parse_style() {
|
||||||
let source = r#"<style type="text/css">
|
let source = r#"<style type="text/css">
|
||||||
|
@ -561,8 +588,10 @@ mod tests {
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod comments {
|
mod comments {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_comments() {
|
fn test_parse_comments() {
|
||||||
let source = "<!-- HTML comment -->{# Django comment #}";
|
let source = "<!-- HTML comment -->{# Django comment #}";
|
||||||
|
@ -573,8 +602,54 @@ mod tests {
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod whitespace {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_with_leading_whitespace() {
|
||||||
|
let source = " hello";
|
||||||
|
let tokens = Lexer::new(source).tokenize().unwrap();
|
||||||
|
let mut parser = Parser::new(tokens);
|
||||||
|
let (ast, errors) = parser.parse().unwrap();
|
||||||
|
insta::assert_yaml_snapshot!(ast);
|
||||||
|
assert!(errors.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_with_leading_whitespace_newline() {
|
||||||
|
let source = "\n hello";
|
||||||
|
let tokens = Lexer::new(source).tokenize().unwrap();
|
||||||
|
let mut parser = Parser::new(tokens);
|
||||||
|
let (ast, errors) = parser.parse().unwrap();
|
||||||
|
insta::assert_yaml_snapshot!(ast);
|
||||||
|
assert!(errors.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_with_trailing_whitespace() {
|
||||||
|
let source = "hello ";
|
||||||
|
let tokens = Lexer::new(source).tokenize().unwrap();
|
||||||
|
let mut parser = Parser::new(tokens);
|
||||||
|
let (ast, errors) = parser.parse().unwrap();
|
||||||
|
insta::assert_yaml_snapshot!(ast);
|
||||||
|
assert!(errors.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_with_trailing_whitespace_newline() {
|
||||||
|
let source = "hello \n";
|
||||||
|
let tokens = Lexer::new(source).tokenize().unwrap();
|
||||||
|
let mut parser = Parser::new(tokens);
|
||||||
|
let (ast, errors) = parser.parse().unwrap();
|
||||||
|
insta::assert_yaml_snapshot!(ast);
|
||||||
|
assert!(errors.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod errors {
|
mod errors {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_unclosed_html_tag() {
|
fn test_parse_unclosed_html_tag() {
|
||||||
let source = "<div>";
|
let source = "<div>";
|
||||||
|
@ -584,6 +659,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_unclosed_django_if() {
|
fn test_parse_unclosed_django_if() {
|
||||||
let source = "{% if user.is_authenticated %}Welcome";
|
let source = "{% if user.is_authenticated %}Welcome";
|
||||||
|
@ -596,6 +672,7 @@ mod tests {
|
||||||
matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "if")
|
matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "if")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_unclosed_django_for() {
|
fn test_parse_unclosed_django_for() {
|
||||||
let source = "{% for item in items %}{{ item.name }}";
|
let source = "{% for item in items %}{{ item.name }}";
|
||||||
|
@ -608,6 +685,7 @@ mod tests {
|
||||||
matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "for")
|
matches!(&errors[0], ParserError::Ast(AstError::UnclosedTag(tag)) if tag == "for")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_unclosed_script() {
|
fn test_parse_unclosed_script() {
|
||||||
let source = "<script>console.log('test');";
|
let source = "<script>console.log('test');";
|
||||||
|
@ -617,6 +695,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_unclosed_style() {
|
fn test_parse_unclosed_style() {
|
||||||
let source = "<style>body { color: blue; ";
|
let source = "<style>body { color: blue; ";
|
||||||
|
@ -626,6 +705,7 @@ mod tests {
|
||||||
insta::assert_yaml_snapshot!(ast);
|
insta::assert_yaml_snapshot!(ast);
|
||||||
assert!(errors.is_empty());
|
assert!(errors.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_error_recovery() {
|
fn test_parse_error_recovery() {
|
||||||
let source = r#"<div class="container">
|
let source = r#"<div class="container">
|
||||||
|
@ -653,6 +733,7 @@ mod tests {
|
||||||
|
|
||||||
mod full_templates {
|
mod full_templates {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_full() {
|
fn test_parse_full() {
|
||||||
let source = r#"<!DOCTYPE html>
|
let source = r#"<!DOCTYPE html>
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
source: crates/djls-template-ast/src/parser.rs
|
||||||
|
assertion_line: 472
|
||||||
|
expression: ast
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
nodes:
|
||||||
|
- Text:
|
||||||
|
content: "<div class=\"container\">Hello</div>"
|
||||||
|
span:
|
||||||
|
start: 0
|
||||||
|
length: 34
|
||||||
|
line_offsets:
|
||||||
|
- 0
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
source: crates/djls-template-ast/src/parser.rs
|
||||||
|
assertion_line: 615
|
||||||
|
expression: ast
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
nodes:
|
||||||
|
- Text:
|
||||||
|
content: " hello"
|
||||||
|
span:
|
||||||
|
start: 0
|
||||||
|
length: 10
|
||||||
|
line_offsets:
|
||||||
|
- 0
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
source: crates/djls-template-ast/src/parser.rs
|
||||||
|
assertion_line: 625
|
||||||
|
expression: ast
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
nodes:
|
||||||
|
- Text:
|
||||||
|
content: hello
|
||||||
|
span:
|
||||||
|
start: 6
|
||||||
|
length: 5
|
||||||
|
line_offsets:
|
||||||
|
- 0
|
||||||
|
- 1
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
source: crates/djls-template-ast/src/parser.rs
|
||||||
|
assertion_line: 635
|
||||||
|
expression: ast
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
nodes:
|
||||||
|
- Text:
|
||||||
|
content: "hello "
|
||||||
|
span:
|
||||||
|
start: 0
|
||||||
|
length: 10
|
||||||
|
line_offsets:
|
||||||
|
- 0
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
source: crates/djls-template-ast/src/parser.rs
|
||||||
|
assertion_line: 645
|
||||||
|
expression: ast
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
nodes:
|
||||||
|
- Text:
|
||||||
|
content: "hello "
|
||||||
|
span:
|
||||||
|
start: 0
|
||||||
|
length: 10
|
||||||
|
line_offsets:
|
||||||
|
- 0
|
||||||
|
- 11
|
Loading…
Add table
Add a link
Reference in a new issue