diff --git a/cli/src/tests/query_test.rs b/cli/src/tests/query_test.rs index c9907cfa..1b147cf9 100644 --- a/cli/src/tests/query_test.rs +++ b/cli/src/tests/query_test.rs @@ -16,37 +16,73 @@ fn test_query_errors_on_invalid_syntax() { // Mismatched parens assert_eq!( Query::new(language, "(if_statement"), - Err(QueryError::Syntax(13)) + Err(QueryError::Syntax("Unexpected EOF".to_string())) ); assert_eq!( - Query::new(language, "(if_statement))"), - Err(QueryError::Syntax(14)) + Query::new(language, "; comment 1\n; comment 2\n (if_statement))"), + Err(QueryError::Syntax( + [ + " (if_statement))", // + " ^", + ] + .join("\n") + )) ); // Return an error at the *beginning* of a bare identifier not followed a colon. // If there's a colon but no pattern, return an error at the end of the colon. assert_eq!( Query::new(language, "(if_statement identifier)"), - Err(QueryError::Syntax(14)) + Err(QueryError::Syntax( + [ + "(if_statement identifier)", // + " ^", + ] + .join("\n") + )) ); assert_eq!( Query::new(language, "(if_statement condition:)"), - Err(QueryError::Syntax(24)) + Err(QueryError::Syntax( + [ + "(if_statement condition:)", // + " ^", + ] + .join("\n") + )) ); // Return an error at the beginning of an unterminated string. assert_eq!( Query::new(language, r#"(identifier) "h "#), - Err(QueryError::Syntax(13)) + Err(QueryError::Syntax( + [ + r#"(identifier) "h "#, // + r#" ^"#, + ] + .join("\n") + )) ); assert_eq!( Query::new(language, r#"((identifier) ()"#), - Err(QueryError::Syntax(16)) + Err(QueryError::Syntax( + [ + "((identifier) ()", // + " ^", + ] + .join("\n") + )) ); assert_eq!( Query::new(language, r#"((identifier) @x (eq? @x a"#), - Err(QueryError::Syntax(26)) + Err(QueryError::Syntax( + [ + r#"((identifier) @x (eq? @x a"#, + r#" ^"#, + ] + .join("\n") + )) ); }); } diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index 1ecde648..491c7db2 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -186,7 +186,7 @@ pub struct QueryCapture<'a> { #[derive(Debug, PartialEq, Eq)] pub enum QueryError { - Syntax(usize), + Syntax(String), NodeType(String), Field(String), Capture(String), @@ -997,6 +997,24 @@ impl Query { // On failure, build an error based on the error code and offset. if ptr.is_null() { let offset = error_offset as usize; + let mut line_start = 0; + let line_containing_error = source.split("\n").find_map(|line| { + let line_end = line_start + line.len() + 1; + if line_end > offset { + Some(line) + } else { + line_start = line_end; + None + } + }); + + let message = if let Some(line) = line_containing_error { + line.to_string() + "\n" + &" ".repeat(offset - line_start) + "^" + } else { + "Unexpected EOF".to_string() + }; + + // if line_containing_error return if error_type != ffi::TSQueryError_TSQueryErrorSyntax { let suffix = source.split_at(offset).1; let end_offset = suffix @@ -1007,10 +1025,10 @@ impl Query { ffi::TSQueryError_TSQueryErrorNodeType => Err(QueryError::NodeType(name)), ffi::TSQueryError_TSQueryErrorField => Err(QueryError::Field(name)), ffi::TSQueryError_TSQueryErrorCapture => Err(QueryError::Capture(name)), - _ => Err(QueryError::Syntax(offset)), + _ => Err(QueryError::Syntax(message)), } } else { - Err(QueryError::Syntax(offset)) + Err(QueryError::Syntax(message)) }; }