@@ -274,6 +274,44 @@ fn test_query_no_match() {
274274 assert ! ( !matched) ;
275275}
276276
277+ #[ test]
278+ fn test_query_skips_extras_in_positional_match ( ) {
279+ // Regression test: positional wildcards `(_)` must not bind to
280+ // tree-sitter `extras` (e.g. comments) during forward-scan; extras
281+ // are conceptually invisible between siblings, matching tree-sitter
282+ // query semantics. Without this, a later rule that translates a
283+ // captured comment to nothing (a common idiom, e.g.
284+ // `(comment) => ()` in Swift) leaves the capture's match-list empty
285+ // and causes the transform to fail with "Variable X has 0 matches".
286+ let runner = Runner :: new ( tree_sitter_ruby:: LANGUAGE . into ( ) , & [ ] ) ;
287+ let ast = runner. run ( "[1, # comment\n 2]" ) . unwrap ( ) ;
288+
289+ // Navigate to the `array` node: program -> array.
290+ let mut cursor = AstCursor :: new ( & ast) ;
291+ cursor. goto_first_child ( ) ;
292+ let array_id = cursor. node_id ( ) ;
293+ assert_eq ! ( ast. get_node( array_id) . unwrap( ) . kind( ) , "array" ) ;
294+
295+ // Two positional wildcards should bind to the two integers, skipping
296+ // the comment that sits between them.
297+ let query = yeast:: query!( ( array ( _) @a ( _) @b) ) ;
298+ let mut captures = yeast:: captures:: Captures :: new ( ) ;
299+ let matched = query. do_match ( & ast, array_id, & mut captures) . unwrap ( ) ;
300+ assert ! ( matched) ;
301+ assert_eq ! (
302+ ast. get_node( captures. get_var( "a" ) . unwrap( ) )
303+ . unwrap( )
304+ . kind( ) ,
305+ "integer"
306+ ) ;
307+ assert_eq ! (
308+ ast. get_node( captures. get_var( "b" ) . unwrap( ) )
309+ . unwrap( )
310+ . kind( ) ,
311+ "integer"
312+ ) ;
313+ }
314+
277315#[ test]
278316fn test_reachable_nodes_excludes_orphaned_rewrite_nodes ( ) {
279317 let lang: tree_sitter:: Language = tree_sitter_ruby:: LANGUAGE . into ( ) ;
0 commit comments