2019-09-09 15:41:13 -07:00
|
|
|
use super::helpers::allocations;
|
|
|
|
|
use super::helpers::fixtures::get_language;
|
|
|
|
|
use tree_sitter::{Parser, Query, QueryError, QueryMatch};
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_query_errors_on_invalid_syntax() {
|
2019-09-11 12:06:38 -07:00
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
|
|
|
|
|
assert!(Query::new(language, "(if_statement)").is_ok());
|
|
|
|
|
assert!(Query::new(language, "(if_statement condition:(identifier))").is_ok());
|
|
|
|
|
|
|
|
|
|
// Mismatched parens
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Query::new(language, "(if_statement"),
|
|
|
|
|
Err(QueryError::Syntax(13))
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Query::new(language, "(if_statement))"),
|
|
|
|
|
Err(QueryError::Syntax(14))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Query::new(language, "(if_statement condition:)"),
|
|
|
|
|
Err(QueryError::Syntax(24))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Query::new(language, "(if_statement condition:)"),
|
|
|
|
|
Err(QueryError::Syntax(24))
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-09-09 15:41:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_query_errors_on_invalid_symbols() {
|
2019-09-11 12:06:38 -07:00
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Query::new(language, "(non_existent1)"),
|
|
|
|
|
Err(QueryError::NodeType("non_existent1"))
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Query::new(language, "(if_statement (non_existent2))"),
|
|
|
|
|
Err(QueryError::NodeType("non_existent2"))
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Query::new(language, "(if_statement condition: (non_existent3))"),
|
|
|
|
|
Err(QueryError::NodeType("non_existent3"))
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
Query::new(language, "(if_statement not_a_field: (identifier))"),
|
|
|
|
|
Err(QueryError::Field("not_a_field"))
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-09-09 15:41:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_query_exec_with_simple_pattern() {
|
2019-09-11 12:06:38 -07:00
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
"(function_declaration name: (identifier) @fn-name)",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let source = "function one() { two(); function three() {} }";
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(source, None).unwrap();
|
|
|
|
|
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source),
|
|
|
|
|
&[
|
|
|
|
|
(0, vec![("fn-name", "one")]),
|
|
|
|
|
(0, vec![("fn-name", "three")])
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-09-09 15:41:13 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_query_exec_with_multiple_matches_same_root() {
|
2019-09-11 12:06:38 -07:00
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
"(class_declaration
|
|
|
|
|
name: (identifier) @the-class-name
|
|
|
|
|
(class_body
|
|
|
|
|
(method_definition
|
|
|
|
|
name: (property_identifier) @the-method-name)))",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let source = "
|
|
|
|
|
class Person {
|
|
|
|
|
// the constructor
|
|
|
|
|
constructor(name) { this.name = name; }
|
|
|
|
|
|
|
|
|
|
// the getter
|
|
|
|
|
getFullName() { return this.name; }
|
|
|
|
|
}
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(source, None).unwrap();
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source),
|
|
|
|
|
&[
|
|
|
|
|
(
|
|
|
|
|
0,
|
|
|
|
|
vec![
|
|
|
|
|
("the-class-name", "Person"),
|
|
|
|
|
("the-method-name", "constructor")
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
0,
|
|
|
|
|
vec![
|
|
|
|
|
("the-class-name", "Person"),
|
|
|
|
|
("the-method-name", "getFullName")
|
|
|
|
|
]
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-09-09 15:41:13 -07:00
|
|
|
}
|
|
|
|
|
|
2019-09-10 22:30:27 -07:00
|
|
|
#[test]
|
2019-09-11 14:44:49 -07:00
|
|
|
fn test_query_exec_multiple_patterns_different_roots() {
|
2019-09-11 12:06:38 -07:00
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
"
|
|
|
|
|
(function_declaration name:(identifier) @fn-def)
|
|
|
|
|
(call_expression function:(identifier) @fn-ref)
|
|
|
|
|
",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let source = "
|
|
|
|
|
function f1() {
|
|
|
|
|
f2(f3());
|
|
|
|
|
}
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(source, None).unwrap();
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source),
|
|
|
|
|
&[
|
|
|
|
|
(0, vec![("fn-def", "f1")]),
|
|
|
|
|
(1, vec![("fn-ref", "f2")]),
|
|
|
|
|
(1, vec![("fn-ref", "f3")]),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-11 14:44:49 -07:00
|
|
|
#[test]
|
|
|
|
|
fn test_query_exec_multiple_patterns_same_root() {
|
|
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
"
|
|
|
|
|
(pair
|
|
|
|
|
key: (property_identifier) @method-def
|
|
|
|
|
value: (function))
|
|
|
|
|
|
|
|
|
|
(pair
|
|
|
|
|
key: (property_identifier) @method-def
|
|
|
|
|
value: (arrow_function))
|
|
|
|
|
",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let source = "
|
|
|
|
|
a = {
|
|
|
|
|
b: () => { return c; },
|
|
|
|
|
d: function() { return d; }
|
|
|
|
|
};
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(source, None).unwrap();
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source),
|
|
|
|
|
&[
|
|
|
|
|
(1, vec![("method-def", "b")]),
|
|
|
|
|
(0, vec![("method-def", "d")]),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-11 12:06:38 -07:00
|
|
|
#[test]
|
|
|
|
|
fn test_query_exec_nested_matches_without_fields() {
|
|
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
"
|
|
|
|
|
(array
|
|
|
|
|
(array
|
|
|
|
|
(identifier) @element-1
|
|
|
|
|
(identifier) @element-2))
|
|
|
|
|
",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let source = "
|
|
|
|
|
[[a]];
|
|
|
|
|
[[c, d], [e, f, g]];
|
|
|
|
|
[[h], [i]];
|
|
|
|
|
";
|
|
|
|
|
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(source, None).unwrap();
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source),
|
|
|
|
|
&[
|
|
|
|
|
(0, vec![("element-1", "c"), ("element-2", "d")]),
|
|
|
|
|
(0, vec![("element-1", "e"), ("element-2", "f")]),
|
|
|
|
|
(0, vec![("element-1", "f"), ("element-2", "g")]),
|
|
|
|
|
(0, vec![("element-1", "e"), ("element-2", "g")]),
|
|
|
|
|
],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_query_exec_many_matches() {
|
|
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(language, "(array (identifier) @element)").unwrap();
|
|
|
|
|
|
|
|
|
|
let source = "[hello];\n".repeat(50);
|
|
|
|
|
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(&source, None).unwrap();
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source.as_str()),
|
|
|
|
|
vec![(0, vec![("element", "hello")]); 50],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_query_exec_too_many_match_permutations_to_track() {
|
|
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
"
|
|
|
|
|
(array (identifier) @pre (identifier) @post)
|
2019-09-10 22:30:27 -07:00
|
|
|
",
|
2019-09-11 12:06:38 -07:00
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let mut source = "hello, ".repeat(50);
|
|
|
|
|
source.insert(0, '[');
|
|
|
|
|
source.push_str("];");
|
|
|
|
|
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(&source, None).unwrap();
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
|
|
|
|
|
// For this pathological query, some match permutations will be dropped.
|
|
|
|
|
// Just check that a subset of the results are returned, and crash or
|
|
|
|
|
// leak occurs.
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source.as_str())[0],
|
|
|
|
|
(0, vec![("pre", "hello"), ("post", "hello")]),
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-09-10 22:30:27 -07:00
|
|
|
}
|
|
|
|
|
|
2019-09-11 14:44:49 -07:00
|
|
|
#[test]
|
|
|
|
|
fn test_query_exec_with_anonymous_tokens() {
|
|
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
r#"
|
|
|
|
|
";" @ punctuation
|
|
|
|
|
"&&" @ operator
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let source = "foo(a && b);";
|
|
|
|
|
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(&source, None).unwrap();
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source),
|
|
|
|
|
&[
|
|
|
|
|
(1, vec![("operator", "&&")]),
|
|
|
|
|
(0, vec![("punctuation", ";")]),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-11 12:16:05 -07:00
|
|
|
#[test]
|
|
|
|
|
fn test_query_capture_names() {
|
|
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
r#"
|
|
|
|
|
(if_statement
|
|
|
|
|
condition: (binary_expression
|
|
|
|
|
left: * @left-operand
|
|
|
|
|
operator: "||"
|
|
|
|
|
right: * @right-operand)
|
|
|
|
|
consequence: (statement_block) @body)
|
|
|
|
|
|
|
|
|
|
(while_statement
|
|
|
|
|
condition:* @loop-condition)
|
|
|
|
|
"#,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
query.capture_names(),
|
|
|
|
|
&[
|
|
|
|
|
"left-operand".to_string(),
|
|
|
|
|
"right-operand".to_string(),
|
|
|
|
|
"body".to_string(),
|
|
|
|
|
"loop-condition".to_string(),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_query_comments() {
|
|
|
|
|
allocations::record(|| {
|
|
|
|
|
let language = get_language("javascript");
|
|
|
|
|
let query = Query::new(
|
|
|
|
|
language,
|
|
|
|
|
"
|
|
|
|
|
; this is my first comment
|
|
|
|
|
; i have two comments here
|
|
|
|
|
(function_declaration
|
|
|
|
|
; there is also a comment here
|
|
|
|
|
; and here
|
|
|
|
|
name: (identifier) @fn-name)",
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let source = "function one() { }";
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
parser.set_language(language).unwrap();
|
|
|
|
|
let tree = parser.parse(source, None).unwrap();
|
|
|
|
|
let context = query.context();
|
|
|
|
|
let matches = context.exec(tree.root_node());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
collect_matches(matches, &query, source),
|
|
|
|
|
&[(0, vec![("fn-name", "one")]),],
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-09 15:41:13 -07:00
|
|
|
fn collect_matches<'a>(
|
|
|
|
|
matches: impl Iterator<Item = QueryMatch<'a>>,
|
|
|
|
|
query: &'a Query,
|
|
|
|
|
source: &'a str,
|
|
|
|
|
) -> Vec<(usize, Vec<(&'a str, &'a str)>)> {
|
|
|
|
|
matches
|
|
|
|
|
.map(|m| {
|
|
|
|
|
(
|
|
|
|
|
m.pattern_index(),
|
|
|
|
|
m.captures()
|
|
|
|
|
.map(|(capture_id, node)| {
|
|
|
|
|
(
|
|
|
|
|
query.capture_names()[capture_id].as_str(),
|
|
|
|
|
node.utf8_text(source.as_bytes()).unwrap(),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect(),
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|