File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -193,10 +193,43 @@ impl Ast {
193193 AstCursor :: new ( self )
194194 }
195195
196+ /// Return all nodes currently allocated in the AST arena.
197+ ///
198+ /// This includes nodes that are no longer reachable from `get_root()`
199+ /// after desugaring rewrites. Use `reachable_node_ids()` for output-level
200+ /// validation/traversal semantics.
196201 pub fn nodes ( & self ) -> & [ Node ] {
197202 & self . nodes
198203 }
199204
205+ /// Return node ids reachable from `get_root()` by following child edges.
206+ ///
207+ /// This reflects the effective AST after desugaring and excludes orphaned
208+ /// arena nodes left behind by rewrite operations.
209+ pub fn reachable_node_ids ( & self ) -> Vec < usize > {
210+ let mut reachable = Vec :: new ( ) ;
211+ let mut stack = vec ! [ self . root] ;
212+ let mut seen = vec ! [ false ; self . nodes. len( ) ] ;
213+
214+ while let Some ( id) = stack. pop ( ) {
215+ if id >= self . nodes . len ( ) || seen[ id] {
216+ continue ;
217+ }
218+ seen[ id] = true ;
219+ reachable. push ( id) ;
220+
221+ if let Some ( node) = self . get_node ( id) {
222+ for children in node. fields . values ( ) {
223+ for & child in children {
224+ stack. push ( child) ;
225+ }
226+ }
227+ }
228+ }
229+
230+ reachable
231+ }
232+
200233 pub fn get_root ( & self ) -> Id {
201234 self . root
202235 }
Original file line number Diff line number Diff line change @@ -166,6 +166,28 @@ fn test_query_no_match() {
166166 assert ! ( !matched) ;
167167}
168168
169+ #[ test]
170+ fn test_reachable_nodes_excludes_orphaned_rewrite_nodes ( ) {
171+ let lang: tree_sitter:: Language = tree_sitter_ruby:: LANGUAGE . into ( ) ;
172+ let schema = yeast:: node_types_yaml:: schema_from_yaml_with_language ( OUTPUT_SCHEMA_YAML , & lang)
173+ . unwrap ( ) ;
174+ let rules = vec ! [ yeast:: rule!( ( integer) => ( identifier "replaced" ) ) ] ;
175+ let runner = Runner :: with_schema ( lang, & schema, & rules) ;
176+
177+ let input = "x = 1" ;
178+ let ast = runner. run ( input) . unwrap ( ) ;
179+ let reachable_ids = ast. reachable_node_ids ( ) ;
180+
181+ assert ! (
182+ ast. nodes( ) . len( ) > reachable_ids. len( ) ,
183+ "expected rewrite to leave orphaned arena nodes"
184+ ) ;
185+
186+ let dump = dump_ast ( & ast, ast. get_root ( ) , input) ;
187+ assert ! ( dump. contains( "identifier \" replaced\" " ) ) ;
188+ assert ! ( !dump. contains( "integer \" 1\" " ) ) ;
189+ }
190+
169191#[ test]
170192fn test_query_repeated_capture ( ) {
171193 let runner = Runner :: new ( tree_sitter_ruby:: LANGUAGE . into ( ) , & [ ] ) ;
You can’t perform that action at this time.
0 commit comments