diff --git a/cli/src/error.rs b/cli/src/error.rs index fab87478..3d5a4745 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -52,7 +52,25 @@ impl Error { impl<'a> From for Error { fn from(error: QueryError) -> Self { - Error::new(format!("{:?}", error)) + match error { + QueryError::Capture(row, c) => Error::new(format!( + "Query error on line {}: Invalid capture name {}", + row, c + )), + QueryError::Field(row, f) => Error::new(format!( + "Query error on line {}: Invalid field name {}", + row, f + )), + QueryError::NodeType(row, t) => Error::new(format!( + "Query error on line {}. Invalid node type {}", + row, t + )), + QueryError::Syntax(row, l) => Error::new(format!( + "Query error on line {}. Invalid syntax:\n{}", + row, l + )), + QueryError::Predicate(p) => Error::new(format!("Query error: {}", p)), + } } } diff --git a/cli/src/main.rs b/cli/src/main.rs index 8f5ac503..22031ac5 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -162,9 +162,15 @@ fn run() -> error::Result<()> { let debug = matches.is_present("debug"); let debug_graph = matches.is_present("debug-graph"); let filter = matches.value_of("filter"); - let corpus_path = current_dir.join("corpus"); if let Some(language) = loader.languages_at_path(¤t_dir)?.first() { - test::run_tests_at_path(*language, &corpus_path, debug, debug_graph, filter)?; + test::run_tests_at_path( + *language, + ¤t_dir.join("corpus"), + debug, + debug_graph, + filter, + )?; + test::check_queries_at_path(*language, ¤t_dir.join("queries"))?; } else { eprintln!("No language found"); } diff --git a/cli/src/test.rs b/cli/src/test.rs index c0f5e6a7..8e7d2968 100644 --- a/cli/src/test.rs +++ b/cli/src/test.rs @@ -10,7 +10,7 @@ use std::fs; use std::io::{self, Write}; use std::path::Path; use std::str; -use tree_sitter::{Language, LogType, Parser}; +use tree_sitter::{Language, LogType, Parser, Query}; lazy_static! { static ref HEADER_REGEX: ByteRegex = ByteRegexBuilder::new(r"^===+\r?\n([^=]*)\r?\n===+\r?\n") @@ -98,6 +98,22 @@ pub fn run_tests_at_path( } } +pub fn check_queries_at_path(language: Language, path: &Path) -> Result<()> { + for entry in fs::read_dir(path)? { + let entry = entry?; + let hidden = entry.file_name().to_str().unwrap_or("").starts_with("."); + if !hidden { + let content = fs::read_to_string(entry.path()).map_err(Error::wrap(|| { + format!("Error reading query file {:?}", entry.file_name()) + }))?; + Query::new(language, &content).map_err(Error::wrap(|| { + format!("Error in query file {:?}", entry.file_name()) + }))?; + } + } + Ok(()) +} + pub fn print_diff_key() { println!( "\n{} / {}", diff --git a/cli/src/tests/query_test.rs b/cli/src/tests/query_test.rs index 9120ce88..8872b850 100644 --- a/cli/src/tests/query_test.rs +++ b/cli/src/tests/query_test.rs @@ -17,6 +17,7 @@ fn test_query_errors_on_invalid_syntax() { assert_eq!( Query::new(language, "(if_statement"), Err(QueryError::Syntax( + 1, [ "(if_statement", // " ^", @@ -27,6 +28,7 @@ fn test_query_errors_on_invalid_syntax() { assert_eq!( Query::new(language, "; comment 1\n; comment 2\n (if_statement))"), Err(QueryError::Syntax( + 3, [ " (if_statement))", // " ^", @@ -40,6 +42,7 @@ fn test_query_errors_on_invalid_syntax() { assert_eq!( Query::new(language, "(if_statement identifier)"), Err(QueryError::Syntax( + 1, [ "(if_statement identifier)", // " ^", @@ -50,6 +53,7 @@ fn test_query_errors_on_invalid_syntax() { assert_eq!( Query::new(language, "(if_statement condition:)"), Err(QueryError::Syntax( + 1, [ "(if_statement condition:)", // " ^", @@ -62,6 +66,7 @@ fn test_query_errors_on_invalid_syntax() { assert_eq!( Query::new(language, r#"(identifier) "h "#), Err(QueryError::Syntax( + 1, [ r#"(identifier) "h "#, // r#" ^"#, @@ -73,6 +78,7 @@ fn test_query_errors_on_invalid_syntax() { assert_eq!( Query::new(language, r#"((identifier) ()"#), Err(QueryError::Syntax( + 1, [ "((identifier) ()", // " ^", @@ -83,6 +89,7 @@ fn test_query_errors_on_invalid_syntax() { assert_eq!( Query::new(language, r#"((identifier) @x (eq? @x a"#), Err(QueryError::Syntax( + 1, [ r#"((identifier) @x (eq? @x a"#, r#" ^"#, @@ -100,23 +107,23 @@ fn test_query_errors_on_invalid_symbols() { assert_eq!( Query::new(language, "(clas)"), - Err(QueryError::NodeType("clas".to_string())) + Err(QueryError::NodeType(1, "clas".to_string())) ); assert_eq!( Query::new(language, "(if_statement (arrayyyyy))"), - Err(QueryError::NodeType("arrayyyyy".to_string())) + Err(QueryError::NodeType(1, "arrayyyyy".to_string())) ); assert_eq!( Query::new(language, "(if_statement condition: (non_existent3))"), - Err(QueryError::NodeType("non_existent3".to_string())) + Err(QueryError::NodeType(1, "non_existent3".to_string())) ); assert_eq!( Query::new(language, "(if_statement condit: (identifier))"), - Err(QueryError::Field("condit".to_string())) + Err(QueryError::Field(1, "condit".to_string())) ); assert_eq!( Query::new(language, "(if_statement conditioning: (identifier))"), - Err(QueryError::Field("conditioning".to_string())) + Err(QueryError::Field(1, "conditioning".to_string())) ); }); } @@ -140,7 +147,7 @@ fn test_query_errors_on_invalid_conditions() { ); assert_eq!( Query::new(language, "((identifier) @id (eq? @id @ok))"), - Err(QueryError::Capture("ok".to_string())) + Err(QueryError::Capture(1, "ok".to_string())) ); }); } diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index 1688dbd3..10ef51e4 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -118,10 +118,10 @@ pub struct QueryCapture<'a> { #[derive(Debug, PartialEq, Eq)] pub enum QueryError { - Syntax(String), - NodeType(String), - Field(String), - Capture(String), + Syntax(usize, String), + NodeType(usize, String), + Field(usize, String), + Capture(usize, String), Predicate(String), } @@ -812,7 +812,9 @@ impl Query { if ptr.is_null() { let offset = error_offset as usize; let mut line_start = 0; + let mut row = 0; let line_containing_error = source.split("\n").find_map(|line| { + row += 1; let line_end = line_start + line.len() + 1; if line_end > offset { Some(line) @@ -836,13 +838,13 @@ impl Query { .unwrap_or(source.len()); let name = suffix.split_at(end_offset).0.to_string(); match error_type { - ffi::TSQueryError_TSQueryErrorNodeType => Err(QueryError::NodeType(name)), - ffi::TSQueryError_TSQueryErrorField => Err(QueryError::Field(name)), - ffi::TSQueryError_TSQueryErrorCapture => Err(QueryError::Capture(name)), - _ => Err(QueryError::Syntax(message)), + ffi::TSQueryError_TSQueryErrorNodeType => Err(QueryError::NodeType(row, name)), + ffi::TSQueryError_TSQueryErrorField => Err(QueryError::Field(row, name)), + ffi::TSQueryError_TSQueryErrorCapture => Err(QueryError::Capture(row, name)), + _ => Err(QueryError::Syntax(row, message)), } } else { - Err(QueryError::Syntax(message)) + Err(QueryError::Syntax(row, message)) }; } diff --git a/lib/src/query.c b/lib/src/query.c index db966dc1..0a613167 100644 --- a/lib/src/query.c +++ b/lib/src/query.c @@ -485,7 +485,7 @@ static TSQueryError ts_query_parse_predicate( // Parse the string content const char *string_content = stream->input; while (stream->next != '"') { - if (!stream_advance(stream)) { + if (stream->next == '\n' || !stream_advance(stream)) { stream_reset(stream, string_content - 1); return TSQueryErrorSyntax; }