Return correct path and line in query errors from the CLI

This commit is contained in:
Max Brunsfeld 2020-09-24 13:47:27 -07:00
parent 297e2bcb28
commit 518916f221
5 changed files with 372 additions and 234 deletions

View file

@ -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<String>);
@ -51,31 +51,19 @@ impl Error {
}
}
impl<'a> From<QueryError> 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)
}
}

View file

@ -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<Option<&HighlightConfiguration>> {
fn include_path_in_error<'a>(
mut error: QueryError,
ranges: &'a Vec<(String, Range<usize>)>,
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<Option<&TagsConfiguration>> {
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<Vec<String>>, default_path: &str) -> Result<String> {
fn read_queries(
&self,
paths: &Option<Vec<String>>,
default_path: &str,
) -> Result<(String, Vec<(String, Range<usize>)>)> {
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))
}
}

View file

@ -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))?;
}
}
}

View file

@ -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")
))
})
);
});
}

View file

@ -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<QueryProperty, QueryError> {
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<ffi::TSInputEdit> 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 {}