Fix bug where patterns with top-level alternatives were not considered 'rooted'

This commit is contained in:
Max Brunsfeld 2022-07-07 17:25:49 -07:00
parent 1401767689
commit 548c12fb88
5 changed files with 169 additions and 22 deletions

View file

@ -1695,31 +1695,54 @@ fn test_query_sibling_patterns_dont_match_children_of_an_error() {
language,
r#"
("{" @open "}" @close)
[
(line_comment)
(block_comment)
] @comment
("<" @first "<" @second)
"#,
)
.unwrap();
// Most of the document will fail to parse, resulting in a
// large number of tokens that are *direct* children of an
// ERROR node.
//
// These children should still match, unless they are part
// of a "non-rooted" pattern, in which there are multiple
// top-level sibling nodes. Those patterns should not match
// directly inside of an error node, because the contents of
// an error node are not syntactically well-structured, so we
// would get many spurious matches.
let source = "
fn a() {}
<<<<<<<<<< add pub b fn () {}
// comment 1
pub fn b() {
/* comment 2 */
==========
pub fn c() {
// comment 3
>>>>>>>>>> add pub c fn () {}
}
";
let mut parser = Parser::new();
parser.set_language(language).unwrap();
let tree = parser.parse(&source, None).unwrap();
let mut cursor = QueryCursor::new();
let matches = cursor.matches(&query, tree.root_node(), source.as_bytes());
assert_eq!(
collect_matches(matches, &query, source),
&[(0, vec![("open", "{"), ("close", "}")])],
&[
(0, vec![("open", "{"), ("close", "}")]),
(1, vec![("comment", "// comment 1")]),
(1, vec![("comment", "/* comment 2 */")]),
(1, vec![("comment", "// comment 3")]),
],
);
});
}
@ -3956,6 +3979,97 @@ fn test_query_is_pattern_guaranteed_at_step() {
});
}
#[test]
fn test_query_is_pattern_rooted() {
struct Row {
description: &'static str,
pattern: &'static str,
is_rooted: bool,
}
let rows = [
Row {
description: "simple token",
pattern: r#"(identifier)"#,
is_rooted: true,
},
Row {
description: "simple non-terminal",
pattern: r#"(function_definition name: (identifier))"#,
is_rooted: true,
},
Row {
description: "alternative of many tokens",
pattern: r#"["if" "def" (identifier) (comment)]"#,
is_rooted: true,
},
Row {
description: "alternative of many non-terminals",
pattern: r#"[
(function_definition name: (identifier))
(class_definition name: (identifier))
(block)
]"#,
is_rooted: true,
},
Row {
description: "two siblings",
pattern: r#"("{" "}")"#,
is_rooted: false,
},
Row {
description: "top-level repetition",
pattern: r#"(comment)*"#,
is_rooted: false,
},
Row {
description: "alternative where one option has two siblings",
pattern: r#"[
(block)
(class_definition)
("(" ")")
(function_definition)
]"#,
is_rooted: false,
},
Row {
description: "alternative where one option has a top-level repetition",
pattern: r#"[
(block)
(class_definition)
(comment)*
(function_definition)
]"#,
is_rooted: false,
},
];
allocations::record(|| {
eprintln!("");
let language = get_language("python");
for row in &rows {
if let Some(filter) = EXAMPLE_FILTER.as_ref() {
if !row.description.contains(filter.as_str()) {
continue;
}
}
eprintln!(" query example: {:?}", row.description);
let query = Query::new(language, row.pattern).unwrap();
assert_eq!(
query.is_pattern_rooted(0),
row.is_rooted,
"Description: {}, Pattern: {:?}",
row.description,
row.pattern
.split_ascii_whitespace()
.collect::<Vec<_>>()
.join(" "),
)
}
});
}
#[test]
fn test_capture_quantifiers() {
struct Row {