diff --git a/cli/src/error.rs b/cli/src/error.rs index 075de3a6..63b57c9e 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -1,7 +1,7 @@ use super::test_highlight; use std::fmt::Write; use std::io; -use tree_sitter::QueryError; +use tree_sitter::{QueryError, QueryErrorKind}; #[derive(Debug)] pub struct Error(pub Vec); @@ -51,31 +51,19 @@ impl Error { } } -impl<'a> From for Error { - fn from(error: QueryError) -> Self { - 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::Structure(row, l) => Error::new(format!( - "Query error on line {}. Impossible pattern:\n{}", - row, l - )), - QueryError::Predicate(p) => Error::new(format!("Query error: {}", p)), +impl<'a> From<(&str, QueryError)> for Error { + fn from((path, error): (&str, QueryError)) -> Self { + let mut msg = format!("Query error at {}:{}. ", path, error.row + 1); + match error.kind { + QueryErrorKind::Capture => write!(&mut msg, "Invalid capture name {}", error.message), + QueryErrorKind::Field => write!(&mut msg, "Invalid field name {}", error.message), + QueryErrorKind::NodeType => write!(&mut msg, "Invalid node type {}", error.message), + QueryErrorKind::Syntax => write!(&mut msg, "Invalid syntax:\n{}", error.message), + QueryErrorKind::Structure => write!(&mut msg, "Impossible pattern:\n{}", error.message), + QueryErrorKind::Predicate => write!(&mut msg, "Invalid predicate: {}", error.message), } + .unwrap(); + Self::new(msg) } } diff --git a/cli/src/loader.rs b/cli/src/loader.rs index 62cc9b62..3d026f21 100644 --- a/cli/src/loader.rs +++ b/cli/src/loader.rs @@ -5,12 +5,13 @@ use regex::{Regex, RegexBuilder}; use serde_derive::Deserialize; use std::collections::HashMap; use std::io::BufReader; +use std::ops::Range; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::Mutex; use std::time::SystemTime; use std::{fs, mem}; -use tree_sitter::Language; +use tree_sitter::{Language, QueryError}; use tree_sitter_highlight::HighlightConfiguration; use tree_sitter_tags::TagsConfiguration; @@ -543,13 +544,32 @@ impl Loader { impl<'a> LanguageConfiguration<'a> { pub fn highlight_config(&self, language: Language) -> Result> { + fn include_path_in_error<'a>( + mut error: QueryError, + ranges: &'a Vec<(String, Range)>, + source: &str, + start_offset: usize, + ) -> (&'a str, QueryError) { + let offset = error.offset - start_offset; + let (path, range) = ranges + .iter() + .find(|(_, range)| range.contains(&offset)) + .unwrap(); + error.row = source[range.start..offset] + .chars() + .filter(|c| *c == '\n') + .count(); + (path.as_ref(), error) + } + self.highlight_config .get_or_try_init(|| { - let highlights_query = + let (highlights_query, highlight_ranges) = self.read_queries(&self.highlights_filenames, "highlights.scm")?; - let injections_query = + let (injections_query, injection_ranges) = self.read_queries(&self.injections_filenames, "injections.scm")?; - let locals_query = self.read_queries(&self.locals_filenames, "locals.scm")?; + let (locals_query, locals_ranges) = + self.read_queries(&self.locals_filenames, "locals.scm")?; if highlights_query.is_empty() { Ok(None) @@ -560,9 +580,25 @@ impl<'a> LanguageConfiguration<'a> { &injections_query, &locals_query, ) - .map_err(Error::wrap(|| { - format!("Failed to load queries in {:?}", self.root_path) - }))?; + .map_err(|error| { + if error.offset < injections_query.len() { + include_path_in_error(error, &injection_ranges, &injections_query, 0) + } else if error.offset < injections_query.len() + locals_query.len() { + include_path_in_error( + error, + &locals_ranges, + &locals_query, + injections_query.len(), + ) + } else { + include_path_in_error( + error, + &highlight_ranges, + &highlights_query, + injections_query.len() + locals_query.len(), + ) + } + })?; let mut all_highlight_names = self.highlight_names.lock().unwrap(); if self.use_all_highlight_names { for capture_name in result.query.capture_names() { @@ -581,8 +617,8 @@ impl<'a> LanguageConfiguration<'a> { pub fn tags_config(&self, language: Language) -> Result> { self.tags_config .get_or_try_init(|| { - let tags_query = self.read_queries(&self.tags_filenames, "tags.scm")?; - let locals_query = self.read_queries(&self.locals_filenames, "locals.scm")?; + let (tags_query, _) = self.read_queries(&self.tags_filenames, "tags.scm")?; + let (locals_query, _) = self.read_queries(&self.locals_filenames, "locals.scm")?; if tags_query.is_empty() { Ok(None) } else { @@ -596,27 +632,34 @@ impl<'a> LanguageConfiguration<'a> { .map(Option::as_ref) } - fn read_queries(&self, paths: &Option>, default_path: &str) -> Result { + fn read_queries( + &self, + paths: &Option>, + default_path: &str, + ) -> Result<(String, Vec<(String, Range)>)> { + let mut query = String::new(); + let mut path_ranges = Vec::new(); if let Some(paths) = paths.as_ref() { - let mut query = String::new(); for path in paths { - let path = self.root_path.join(path); - query += &fs::read_to_string(&path).map_err(Error::wrap(|| { + let abs_path = self.root_path.join(path); + let prev_query_len = query.len(); + query += &fs::read_to_string(&abs_path).map_err(Error::wrap(|| { format!("Failed to read query file {:?}", path) }))?; + path_ranges.push((path.clone(), prev_query_len..query.len())); } - Ok(query) } else { let queries_path = self.root_path.join("queries"); let path = queries_path.join(default_path); if path.exists() { - fs::read_to_string(&path).map_err(Error::wrap(|| { + query = fs::read_to_string(&path).map_err(Error::wrap(|| { format!("Failed to read query file {:?}", path) - })) - } else { - Ok(String::new()) + }))?; + path_ranges.push((default_path.to_string(), 0..query.len())); } } + + Ok((query, path_ranges)) } } diff --git a/cli/src/test.rs b/cli/src/test.rs index 1806c150..7c143ecd 100644 --- a/cli/src/test.rs +++ b/cli/src/test.rs @@ -102,14 +102,14 @@ pub fn check_queries_at_path(language: Language, path: &Path) -> Result<()> { if path.exists() { for entry in fs::read_dir(path)? { let entry = entry?; - let hidden = entry.file_name().to_str().unwrap_or("").starts_with("."); + let filepath = entry.file_name(); + let filepath = filepath.to_str().unwrap_or(""); + let hidden = filepath.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()) - }))?; + Query::new(language, &content).map_err(|e| (filepath, e))?; } } } diff --git a/cli/src/tests/query_test.rs b/cli/src/tests/query_test.rs index 5fd07680..2b816bbc 100644 --- a/cli/src/tests/query_test.rs +++ b/cli/src/tests/query_test.rs @@ -4,8 +4,8 @@ use lazy_static::lazy_static; use std::env; use std::fmt::Write; use tree_sitter::{ - Language, Node, Parser, Query, QueryCapture, QueryCursor, QueryError, QueryMatch, - QueryPredicate, QueryPredicateArg, QueryProperty, + Language, Node, Parser, Query, QueryCapture, QueryCursor, QueryError, QueryErrorKind, + QueryMatch, QueryPredicate, QueryPredicateArg, QueryProperty, }; lazy_static! { @@ -26,109 +26,98 @@ fn test_query_errors_on_invalid_syntax() { // Mismatched parens assert_eq!( - Query::new(language, "(if_statement"), - Err(QueryError::Syntax( - 1, - [ - "(if_statement", // - " ^", - ] - .join("\n") - )) + Query::new(language, "(if_statement").unwrap_err().message, + [ + "(if_statement", // + " ^", + ] + .join("\n") ); assert_eq!( - Query::new(language, "; comment 1\n; comment 2\n (if_statement))"), - Err(QueryError::Syntax( - 3, - [ - " (if_statement))", // - " ^", - ] - .join("\n") - )) + Query::new(language, "; comment 1\n; comment 2\n (if_statement))") + .unwrap_err() + .message, + [ + " (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( - 1, - [ - "(if_statement identifier)", // - " ^", - ] - .join("\n") - )) + Query::new(language, "(if_statement identifier)") + .unwrap_err() + .message, + [ + "(if_statement identifier)", // + " ^", + ] + .join("\n") ); assert_eq!( - Query::new(language, "(if_statement condition:)"), - Err(QueryError::Syntax( - 1, - [ - "(if_statement condition:)", // - " ^", - ] - .join("\n") - )) + Query::new(language, "(if_statement condition:)") + .unwrap_err() + .message, + [ + "(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( - 1, - [ - r#"(identifier) "h "#, // - r#" ^"#, - ] - .join("\n") - )) + Query::new(language, r#"(identifier) "h "#) + .unwrap_err() + .message, + [ + r#"(identifier) "h "#, // + r#" ^"#, + ] + .join("\n") ); assert_eq!( - Query::new(language, r#"((identifier) ()"#), - Err(QueryError::Syntax( - 1, - [ - "((identifier) ()", // - " ^", - ] - .join("\n") - )) + Query::new(language, r#"((identifier) ()"#) + .unwrap_err() + .message, + [ + "((identifier) ()", // + " ^", + ] + .join("\n") ); assert_eq!( - Query::new(language, r#"((identifier) [])"#), - Err(QueryError::Syntax( - 1, - [ - "((identifier) [])", // - " ^", - ] - .join("\n") - )) + Query::new(language, r#"((identifier) [])"#) + .unwrap_err() + .message, + [ + "((identifier) [])", // + " ^", + ] + .join("\n") ); assert_eq!( - Query::new(language, r#"((identifier) (#a)"#), - Err(QueryError::Syntax( - 1, - [ - "((identifier) (#a)", // - " ^", - ] - .join("\n") - )) + Query::new(language, r#"((identifier) (#a)"#) + .unwrap_err() + .message, + [ + "((identifier) (#a)", // + " ^", + ] + .join("\n") ); assert_eq!( - Query::new(language, r#"((identifier) @x (#eq? @x a"#), - Err(QueryError::Syntax( - 1, - [ - r#"((identifier) @x (#eq? @x a"#, - r#" ^"#, - ] - .join("\n") - )) + Query::new(language, r#"((identifier) @x (#eq? @x a"#) + .unwrap_err() + .message, + [ + r#"((identifier) @x (#eq? @x a"#, + r#" ^"#, + ] + .join("\n") ); }); } @@ -139,53 +128,97 @@ fn test_query_errors_on_invalid_symbols() { let language = get_language("javascript"); assert_eq!( - Query::new(language, "(clas)"), - Err(QueryError::NodeType(1, "clas".to_string())) + Query::new(language, "(clas)").unwrap_err(), + QueryError { + row: 1, + offset: 1, + column: 1, + kind: QueryErrorKind::NodeType, + message: "clas".to_string() + } ); assert_eq!( - Query::new(language, "(if_statement (arrayyyyy))"), - Err(QueryError::NodeType(1, "arrayyyyy".to_string())) + Query::new(language, "(if_statement (arrayyyyy))").unwrap_err(), + QueryError { + row: 1, + offset: 15, + column: 15, + kind: QueryErrorKind::NodeType, + message: "arrayyyyy".to_string() + }, ); assert_eq!( - Query::new(language, "(if_statement condition: (non_existent3))"), - Err(QueryError::NodeType(1, "non_existent3".to_string())) + Query::new(language, "(if_statement condition: (non_existent3))").unwrap_err(), + QueryError { + row: 1, + offset: 26, + column: 26, + kind: QueryErrorKind::NodeType, + message: "non_existent3".to_string() + }, ); assert_eq!( - Query::new(language, "(if_statement condit: (identifier))"), - Err(QueryError::Field(1, "condit".to_string())) + Query::new(language, "(if_statement condit: (identifier))").unwrap_err(), + QueryError { + row: 1, + offset: 14, + column: 14, + kind: QueryErrorKind::Field, + message: "condit".to_string() + }, ); assert_eq!( - Query::new(language, "(if_statement conditioning: (identifier))"), - Err(QueryError::Field(1, "conditioning".to_string())) + Query::new(language, "(if_statement conditioning: (identifier))").unwrap_err(), + QueryError { + row: 1, + offset: 14, + column: 14, + kind: QueryErrorKind::Field, + message: "conditioning".to_string() + } ); }); } #[test] -fn test_query_errors_on_invalid_conditions() { +fn test_query_errors_on_invalid_predicates() { allocations::record(|| { let language = get_language("javascript"); assert_eq!( - Query::new(language, "((identifier) @id (@id))"), - Err(QueryError::Syntax( - 1, - [ + Query::new(language, "((identifier) @id (@id))").unwrap_err(), + QueryError { + kind: QueryErrorKind::Syntax, + row: 1, + column: 19, + offset: 19, + message: [ "((identifier) @id (@id))", // " ^" ] .join("\n") - )) + } ); assert_eq!( - Query::new(language, "((identifier) @id (#eq? @id))"), - Err(QueryError::Predicate( - "Wrong number of arguments to #eq? predicate. Expected 2, got 1.".to_string() - )) + Query::new(language, "((identifier) @id (#eq? @id))").unwrap_err(), + QueryError { + kind: QueryErrorKind::Predicate, + row: 0, + column: 0, + offset: 0, + message: "Wrong number of arguments to #eq? predicate. Expected 2, got 1." + .to_string() + } ); assert_eq!( - Query::new(language, "((identifier) @id (#eq? @id @ok))"), - Err(QueryError::Capture(1, "ok".to_string())) + Query::new(language, "((identifier) @id (#eq? @id @ok))").unwrap_err(), + QueryError { + kind: QueryErrorKind::Capture, + row: 1, + column: 29, + offset: 29, + message: "ok".to_string(), + } ); }); } @@ -201,14 +234,17 @@ fn test_query_errors_on_impossible_patterns() { js_lang, "(binary_expression left: (identifier) left: (identifier))" ), - Err(QueryError::Structure( - 1, - [ + Err(QueryError { + kind: QueryErrorKind::Structure, + row: 1, + offset: 38, + column: 38, + message: [ "(binary_expression left: (identifier) left: (identifier))", " ^" ] .join("\n"), - )) + }) ); Query::new( @@ -218,27 +254,33 @@ fn test_query_errors_on_impossible_patterns() { .unwrap(); assert_eq!( Query::new(js_lang, "(function_declaration name: (statement_block))"), - Err(QueryError::Structure( - 1, - [ + Err(QueryError { + kind: QueryErrorKind::Structure, + row: 1, + offset: 22, + column: 22, + message: [ "(function_declaration name: (statement_block))", " ^", ] .join("\n") - )) + }) ); Query::new(rb_lang, "(call receiver:(call))").unwrap(); assert_eq!( Query::new(rb_lang, "(call receiver:(binary))"), - Err(QueryError::Structure( - 1, - [ + Err(QueryError { + kind: QueryErrorKind::Structure, + row: 1, + offset: 6, + column: 6, + message: [ "(call receiver:(binary))", // " ^", ] .join("\n") - )) + }) ); Query::new( @@ -259,37 +301,46 @@ fn test_query_errors_on_impossible_patterns() { (generator_function_declaration (identifier)) ]", ), - Err(QueryError::Structure( - 3, - [ + Err(QueryError { + kind: QueryErrorKind::Structure, + row: 3, + offset: 88, + column: 42, + message: [ " (function_declaration (object))", // " ^", ] .join("\n") - )) + }) ); assert_eq!( Query::new(js_lang, "(identifier (identifier))",), - Err(QueryError::Structure( - 1, - [ + Err(QueryError { + kind: QueryErrorKind::Structure, + row: 1, + offset: 12, + column: 12, + message: [ "(identifier (identifier))", // " ^", ] .join("\n") - )) + }) ); assert_eq!( Query::new(js_lang, "(true (true))",), - Err(QueryError::Structure( - 1, - [ + Err(QueryError { + kind: QueryErrorKind::Structure, + row: 1, + offset: 6, + column: 6, + message: [ "(true (true))", // " ^", ] .join("\n") - )) + }) ); Query::new( @@ -298,16 +349,20 @@ fn test_query_errors_on_impossible_patterns() { condition: (parenthesized_expression (_expression) @cond))", ) .unwrap(); + assert_eq!( Query::new(js_lang, "(if_statement condition: (_expression))",), - Err(QueryError::Structure( - 1, - [ + Err(QueryError { + kind: QueryErrorKind::Structure, + row: 1, + offset: 14, + column: 14, + message: [ "(if_statement condition: (_expression))", // " ^", ] .join("\n") - )) + }) ); }); } diff --git a/lib/binding_rust/lib.rs b/lib/binding_rust/lib.rs index ea5893b4..75ed361f 100644 --- a/lib/binding_rust/lib.rs +++ b/lib/binding_rust/lib.rs @@ -157,13 +157,22 @@ pub struct IncludedRangesError(pub usize); /// An error that occurred when trying to create a `Query`. #[derive(Debug, PartialEq, Eq)] -pub enum QueryError { - Syntax(usize, String), - NodeType(usize, String), - Field(usize, String), - Capture(usize, String), - Predicate(String), - Structure(usize, String), +pub struct QueryError { + pub row: usize, + pub column: usize, + pub offset: usize, + pub message: String, + pub kind: QueryErrorKind, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum QueryErrorKind { + Syntax, + NodeType, + Field, + Capture, + Predicate, + Structure, } #[derive(Debug)] @@ -1175,8 +1184,11 @@ impl Query { None } }); + let column = offset - line_start; - return match error_type { + let kind; + let message; + match error_type { // Error types that report names ffi::TSQueryError_TSQueryErrorNodeType | ffi::TSQueryError_TSQueryErrorField @@ -1185,34 +1197,36 @@ impl Query { let end_offset = suffix .find(|c| !char::is_alphanumeric(c) && c != '_' && c != '-') .unwrap_or(source.len()); - let name = suffix.split_at(end_offset).0.to_string(); - match error_type { - ffi::TSQueryError_TSQueryErrorNodeType => { - Err(QueryError::NodeType(row, name)) - } - ffi::TSQueryError_TSQueryErrorField => Err(QueryError::Field(row, name)), - ffi::TSQueryError_TSQueryErrorCapture => { - Err(QueryError::Capture(row, name)) - } + message = suffix.split_at(end_offset).0.to_string(); + kind = match error_type { + ffi::TSQueryError_TSQueryErrorNodeType => QueryErrorKind::NodeType, + ffi::TSQueryError_TSQueryErrorField => QueryErrorKind::Field, + ffi::TSQueryError_TSQueryErrorCapture => QueryErrorKind::Capture, _ => unreachable!(), - } + }; } // Error types that report positions _ => { - let message = if let Some(line) = line_containing_error { + message = if let Some(line) = line_containing_error { line.to_string() + "\n" + &" ".repeat(offset - line_start) + "^" } else { "Unexpected EOF".to_string() }; - match error_type { - ffi::TSQueryError_TSQueryErrorStructure => { - Err(QueryError::Structure(row, message)) - } - _ => Err(QueryError::Syntax(row, message)), - } + kind = match error_type { + ffi::TSQueryError_TSQueryErrorStructure => QueryErrorKind::Structure, + _ => QueryErrorKind::Syntax, + }; } }; + + return Err(QueryError { + row, + column, + offset, + kind, + message, + }); } let string_count = unsafe { ffi::ts_query_string_count(ptr) }; @@ -1261,6 +1275,13 @@ impl Query { slice::from_raw_parts(raw_predicates, length as usize) }; + let byte_offset = unsafe { ffi::ts_query_start_byte_for_pattern(ptr, i as u32) }; + let row = source + .char_indices() + .take_while(|(i, _)| *i < byte_offset as usize) + .filter(|(_, c)| *c == '\n') + .count(); + let type_done = ffi::TSQueryPredicateStepType_TSQueryPredicateStepTypeDone; let type_capture = ffi::TSQueryPredicateStepType_TSQueryPredicateStepTypeCapture; let type_string = ffi::TSQueryPredicateStepType_TSQueryPredicateStepTypeString; @@ -1275,10 +1296,13 @@ impl Query { } if p[0].type_ != type_string { - return Err(QueryError::Predicate(format!( - "Expected predicate to start with a function name. Got @{}.", - result.capture_names[p[0].value_id as usize], - ))); + return Err(predicate_error( + row, + format!( + "Expected predicate to start with a function name. Got @{}.", + result.capture_names[p[0].value_id as usize], + ), + )); } // Build a predicate for each of the known predicate function names. @@ -1286,13 +1310,16 @@ impl Query { match operator_name.as_str() { "eq?" | "not-eq?" => { if p.len() != 3 { - return Err(QueryError::Predicate(format!( + return Err(predicate_error( + row, + format!( "Wrong number of arguments to #eq? predicate. Expected 2, got {}.", p.len() - 1 - ))); + ), + )); } if p[1].type_ != type_capture { - return Err(QueryError::Predicate(format!( + return Err(predicate_error(row, format!( "First argument to #eq? predicate must be a capture name. Got literal \"{}\".", string_values[p[1].value_id as usize], ))); @@ -1316,19 +1343,19 @@ impl Query { "match?" | "not-match?" => { if p.len() != 3 { - return Err(QueryError::Predicate(format!( + return Err(predicate_error(row, format!( "Wrong number of arguments to #match? predicate. Expected 2, got {}.", p.len() - 1 ))); } if p[1].type_ != type_capture { - return Err(QueryError::Predicate(format!( + return Err(predicate_error(row, format!( "First argument to #match? predicate must be a capture name. Got literal \"{}\".", string_values[p[1].value_id as usize], ))); } if p[2].type_ == type_capture { - return Err(QueryError::Predicate(format!( + return Err(predicate_error(row, format!( "Second argument to #match? predicate must be a literal. Got capture @{}.", result.capture_names[p[2].value_id as usize], ))); @@ -1339,14 +1366,15 @@ impl Query { text_predicates.push(TextPredicate::CaptureMatchString( p[1].value_id, regex::bytes::Regex::new(regex).map_err(|_| { - QueryError::Predicate(format!("Invalid regex '{}'", regex)) + predicate_error(row, format!("Invalid regex '{}'", regex)) })?, is_positive, )); } "set!" => property_settings.push(Self::parse_property( - "set!", + row, + &operator_name, &result.capture_names, &string_values, &p[1..], @@ -1354,6 +1382,7 @@ impl Query { "is?" | "is-not?" => property_predicates.push(( Self::parse_property( + row, &operator_name, &result.capture_names, &string_values, @@ -1476,17 +1505,21 @@ impl Query { } fn parse_property( + row: usize, function_name: &str, capture_names: &[String], string_values: &[String], args: &[ffi::TSQueryPredicateStep], ) -> Result { if args.len() == 0 || args.len() > 3 { - return Err(QueryError::Predicate(format!( - "Wrong number of arguments to {} predicate. Expected 1 to 3, got {}.", - function_name, - args.len(), - ))); + return Err(predicate_error( + row, + format!( + "Wrong number of arguments to {} predicate. Expected 1 to 3, got {}.", + function_name, + args.len(), + ), + )); } let mut capture_id = None; @@ -1496,10 +1529,13 @@ impl Query { for arg in args { if arg.type_ == ffi::TSQueryPredicateStepType_TSQueryPredicateStepTypeCapture { if capture_id.is_some() { - return Err(QueryError::Predicate(format!( - "Invalid arguments to {} predicate. Unexpected second capture name @{}", - function_name, capture_names[arg.value_id as usize] - ))); + return Err(predicate_error( + row, + format!( + "Invalid arguments to {} predicate. Unexpected second capture name @{}", + function_name, capture_names[arg.value_id as usize] + ), + )); } capture_id = Some(arg.value_id as usize); } else if key.is_none() { @@ -1507,20 +1543,26 @@ impl Query { } else if value.is_none() { value = Some(string_values[arg.value_id as usize].as_str()); } else { - return Err(QueryError::Predicate(format!( - "Invalid arguments to {} predicate. Unexpected third argument @{}", - function_name, string_values[arg.value_id as usize] - ))); + return Err(predicate_error( + row, + format!( + "Invalid arguments to {} predicate. Unexpected third argument @{}", + function_name, string_values[arg.value_id as usize] + ), + )); } } if let Some(key) = key { Ok(QueryProperty::new(key, value, capture_id)) } else { - return Err(QueryError::Predicate(format!( - "Invalid arguments to {} predicate. Missing key argument", - function_name, - ))); + return Err(predicate_error( + row, + format!( + "Invalid arguments to {} predicate. Missing key argument", + function_name, + ), + )); } } } @@ -1770,6 +1812,16 @@ impl<'a> Into for &'a InputEdit { } } +fn predicate_error(row: usize, message: String) -> QueryError { + QueryError { + kind: QueryErrorKind::Predicate, + row, + column: 0, + offset: 0, + message, + } +} + unsafe impl Send for Language {} unsafe impl Send for Parser {} unsafe impl Send for Query {}