Yeast: better support for rewriting unnamed nodes

- Ensure the full wildcard _ supports quantifiers
- Also rewrite unnamed nodes in one-shot phases
This commit is contained in:
Asger F
2026-06-02 13:15:22 +02:00
parent 00068948c1
commit ddc9516e92
3 changed files with 30 additions and 10 deletions

View File

@@ -259,6 +259,7 @@ fn parse_query_list(tokens: &mut Tokens) -> Result<Vec<TokenStream>> {
yeast::query::QueryListElem::SingleNode(#node)
},
)?;
let elem = maybe_wrap_list_capture(tokens, elem)?;
elems.push(elem);
continue;
}
@@ -276,6 +277,7 @@ fn parse_query_list(tokens: &mut Tokens) -> Result<Vec<TokenStream>> {
yeast::query::QueryListElem::SingleNode(#node)
},
)?;
let elem = maybe_wrap_list_capture(tokens, elem)?;
elems.push(elem);
continue;
}
@@ -717,8 +719,11 @@ fn extract_captures_inner(
}
last_mult = CaptureMultiplicity::Single;
}
TokenTree::Punct(p) if matches!(p.as_char(), '*' | '+' | '?') => {
// Keep last_mult — the @capture follows
TokenTree::Punct(p) if p.as_char() == '*' || p.as_char() == '+' => {
last_mult = CaptureMultiplicity::Repeated;
}
TokenTree::Punct(p) if p.as_char() == '?' => {
last_mult = CaptureMultiplicity::Optional;
}
_ => {
last_mult = CaptureMultiplicity::Single;

View File

@@ -825,14 +825,6 @@ fn apply_one_shot_rules_inner(
let node_kind = ast.get_node(id).map(|n| n.kind()).unwrap_or("");
// Don't rewrite unnamed nodes (punctuation, keywords, etc.); leave them
// as-is. Rules target named nodes only.
if let Some(node) = ast.get_node(id) {
if !node.is_named() {
return Ok(vec![id]);
}
}
for rule in index.rules_for_kind(node_kind) {
if let Some(mut captures) = rule.try_match(ast, id)? {
// Recursively translate every captured node before invoking the

View File

@@ -389,6 +389,29 @@ fn test_capture_unnamed_node_parenthesized() {
assert!(!op_node.is_named());
}
#[test]
fn test_capture_bare_underscore_repeated() {
// `_` matches named and unnamed nodes in bare-child position. On this
// assignment shape, bare children correspond to unnamed tokens (the `=`).
let runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), &[]);
let ast = runner.run("x = 1").unwrap();
let query = yeast::query!((assignment _* @all));
let mut cursor = AstCursor::new(&ast);
cursor.goto_first_child();
let assignment_id = cursor.node_id();
let mut captures = yeast::captures::Captures::new();
let matched = query.do_match(&ast, assignment_id, &mut captures).unwrap();
assert!(matched);
let all = captures.get_all("all");
assert_eq!(all.len(), 1);
assert_eq!(ast.get_node(all[0]).unwrap().kind(), "=");
assert!(!ast.get_node(all[0]).unwrap().is_named());
}
#[test]
fn test_capture_unnamed_node_bare_literal() {
// `"=" @op` (without surrounding parens) is the same as `("=") @op`.