Skip to content

Commit 49f1909

Browse files
committed
Yeast: add reachable_node_ids()
1 parent f668b99 commit 49f1909

2 files changed

Lines changed: 55 additions & 0 deletions

File tree

shared/yeast/src/lib.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff 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
}

shared/yeast/tests/test.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff 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]
170192
fn test_query_repeated_capture() {
171193
let runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), &[]);

0 commit comments

Comments
 (0)