Return correct path and line in query errors from the CLI
This commit is contained in:
parent
297e2bcb28
commit
518916f221
5 changed files with 372 additions and 234 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
))
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue