diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 5ee3156bcd4..169b1c91c0c 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -259,6 +259,7 @@ fn parse_query_list(tokens: &mut Tokens) -> Result> { 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> { 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; diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 0717d27e4b8..63ad76625bb 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -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 diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index 7645f3776f8..3ae8e2072d9 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -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`.