1use std::collections::hash_map::DefaultHasher;
2use std::hash::{Hash, Hasher};
3
4use cha_core::{ClassInfo, FunctionInfo, ImportInfo, SourceFile, SourceModel};
5use tree_sitter::{Node, Parser};
6
7use crate::LanguageParser;
8
9pub struct TypeScriptParser;
10
11impl LanguageParser for TypeScriptParser {
12 fn language_name(&self) -> &str {
13 "typescript"
14 }
15
16 fn parse(&self, file: &SourceFile) -> Option<SourceModel> {
17 let mut parser = Parser::new();
18 parser
19 .set_language(&tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into())
20 .ok()?;
21 let tree = parser.parse(&file.content, None)?;
22 let root = tree.root_node();
23 let src = file.content.as_bytes();
24
25 let mut ctx = ParseContext::new(src);
26 ctx.collect_nodes(root, false);
27
28 Some(SourceModel {
29 language: "typescript".into(),
30 total_lines: file.line_count(),
31 functions: ctx.col.functions,
32 classes: ctx.col.classes,
33 imports: ctx.col.imports,
34 comments: collect_comments(root, src),
35 type_aliases: vec![], })
37 }
38}
39
40struct Collector {
42 functions: Vec<FunctionInfo>,
43 classes: Vec<ClassInfo>,
44 imports: Vec<ImportInfo>,
45}
46
47struct ParseContext<'a> {
49 src: &'a [u8],
50 col: Collector,
51}
52
53impl<'a> ParseContext<'a> {
54 fn new(src: &'a [u8]) -> Self {
55 Self {
56 src,
57 col: Collector {
58 functions: Vec::new(),
59 classes: Vec::new(),
60 imports: Vec::new(),
61 },
62 }
63 }
64
65 fn collect_nodes(&mut self, node: Node, exported: bool) {
66 let mut cursor = node.walk();
67 for child in node.children(&mut cursor) {
68 self.collect_single_node(child, exported);
69 }
70 }
71
72 fn collect_single_node(&mut self, child: Node, exported: bool) {
73 match child.kind() {
74 "export_statement" => self.collect_nodes(child, true),
75 "function_declaration" | "method_definition" => self.push_function(child, exported),
76 "lexical_declaration" | "variable_declaration" => {
77 extract_arrow_functions(child, self.src, exported, &mut self.col.functions);
78 self.collect_nodes(child, exported);
79 }
80 "class_declaration" => self.push_class(child, exported),
81 "import_statement" => self.push_import(child),
82 _ => self.collect_nodes(child, false),
83 }
84 }
85
86 fn push_function(&mut self, node: Node, exported: bool) {
87 if let Some(mut f) = extract_function(node, self.src) {
88 f.is_exported = exported;
89 self.col.functions.push(f);
90 }
91 }
92
93 fn push_class(&mut self, node: Node, exported: bool) {
94 if let Some(mut c) = extract_class(node, self.src) {
95 c.is_exported = exported;
96 self.col.classes.push(c);
97 }
98 }
99
100 fn push_import(&mut self, node: Node) {
101 if let Some(i) = extract_import(node, self.src) {
102 self.col.imports.push(i);
103 }
104 }
105}
106
107fn node_text<'a>(node: Node, src: &'a [u8]) -> &'a str {
108 node.utf8_text(src).unwrap_or("")
109}
110
111fn hash_ast_structure(node: Node) -> u64 {
113 let mut hasher = DefaultHasher::new();
114 walk_hash(node, &mut hasher);
115 hasher.finish()
116}
117
118fn walk_hash(node: Node, hasher: &mut DefaultHasher) {
119 node.kind().hash(hasher);
120 let mut cursor = node.walk();
121 for child in node.children(&mut cursor) {
122 walk_hash(child, hasher);
123 }
124}
125
126fn extract_function(node: Node, src: &[u8]) -> Option<FunctionInfo> {
127 let name_node = node.child_by_field_name("name")?;
128 let name = node_text(name_node, src).to_string();
129 let name_col = name_node.start_position().column;
130 let name_end_col = name_node.end_position().column;
131 let start_line = node.start_position().row + 1;
132 let end_line = node.end_position().row + 1;
133 let body = node.child_by_field_name("body");
134 let body_hash = body.map(hash_ast_structure);
135 let parameter_count = count_parameters(node);
136 let parameter_types = extract_param_types(node, src);
137 let chain_depth = body.map(max_chain_depth).unwrap_or(0);
138 let switch_arms = body.map(count_switch_arms).unwrap_or(0);
139 let external_refs = body
140 .map(|b| collect_external_refs(b, src))
141 .unwrap_or_default();
142 let is_delegating = body.map(|b| check_delegating(b, src)).unwrap_or(false);
143 Some(FunctionInfo {
144 name,
145 start_line,
146 end_line,
147 name_col,
148 name_end_col,
149 line_count: end_line - start_line + 1,
150 complexity: count_complexity(node),
151 body_hash,
152 is_exported: false,
153 parameter_count,
154 parameter_types,
155 chain_depth,
156 switch_arms,
157 external_refs,
158 is_delegating,
159 comment_lines: count_comment_lines(node),
160 referenced_fields: collect_this_fields(body, src),
161 null_check_fields: collect_null_checks_ts(body, src),
162 switch_dispatch_target: extract_switch_target_ts(body, src),
163 optional_param_count: count_optional_params_ts(node, src),
164 called_functions: collect_calls_ts(body, src),
165 cognitive_complexity: body.map(cognitive_complexity_ts).unwrap_or(0),
166 })
167}
168
169fn extract_arrow_functions(
170 node: Node,
171 src: &[u8],
172 exported: bool,
173 functions: &mut Vec<FunctionInfo>,
174) {
175 let mut cursor = node.walk();
176 for child in node.children(&mut cursor) {
177 if child.kind() == "variable_declarator"
178 && let Some(f) = try_extract_arrow(child, node, src, exported)
179 {
180 functions.push(f);
181 }
182 }
183}
184
185fn try_extract_arrow(child: Node, decl: Node, src: &[u8], exported: bool) -> Option<FunctionInfo> {
187 let name_node = child.child_by_field_name("name")?;
188 let name = node_text(name_node, src).to_string();
189 let value = child.child_by_field_name("value")?;
190 if value.kind() != "arrow_function" {
191 return None;
192 }
193 let name_col = name_node.start_position().column;
194 let name_end_col = name_node.end_position().column;
195 let start_line = decl.start_position().row + 1;
196 let end_line = decl.end_position().row + 1;
197 let body = value.child_by_field_name("body");
198 let body_hash = body.map(hash_ast_structure);
199 Some(FunctionInfo {
200 name,
201 start_line,
202 end_line,
203 name_col,
204 name_end_col,
205 line_count: end_line - start_line + 1,
206 complexity: count_complexity(value),
207 body_hash,
208 is_exported: exported,
209 parameter_count: count_parameters(value),
210 parameter_types: extract_param_types(value, src),
211 chain_depth: body.map(max_chain_depth).unwrap_or(0),
212 switch_arms: body.map(count_switch_arms).unwrap_or(0),
213 external_refs: body
214 .map(|b| collect_external_refs(b, src))
215 .unwrap_or_default(),
216 is_delegating: body.map(|b| check_delegating(b, src)).unwrap_or(false),
217 comment_lines: count_comment_lines(value),
218 referenced_fields: collect_this_fields(body, src),
219 null_check_fields: collect_null_checks_ts(body, src),
220 switch_dispatch_target: extract_switch_target_ts(body, src),
221 optional_param_count: count_optional_params_ts(value, src),
222 called_functions: collect_calls_ts(Some(value), src),
223 cognitive_complexity: cognitive_complexity_ts(value),
224 })
225}
226
227fn extract_class(node: Node, src: &[u8]) -> Option<ClassInfo> {
228 let name_node = node.child_by_field_name("name")?;
229 let name = node_text(name_node, src).to_string();
230 let name_col = name_node.start_position().column;
231 let name_end_col = name_node.end_position().column;
232 let start_line = node.start_position().row + 1;
233 let end_line = node.end_position().row + 1;
234 let body = node.child_by_field_name("body")?;
235 let (methods, delegating, fields, has_behavior, cb_fields) = scan_class_body(body, src);
236 let is_interface =
237 node.kind() == "interface_declaration" || node.kind() == "abstract_class_declaration";
238 let has_listener_field = !cb_fields.is_empty();
239 let has_notify_method = has_iterate_and_call_ts(body, src, &cb_fields);
240
241 Some(ClassInfo {
242 name,
243 start_line,
244 end_line,
245 name_col,
246 name_end_col,
247 method_count: methods,
248 line_count: end_line - start_line + 1,
249 is_exported: false,
250 delegating_method_count: delegating,
251 field_count: fields.len(),
252 field_names: fields,
253 field_types: Vec::new(),
254 has_behavior,
255 is_interface,
256 parent_name: extract_parent_name(node, src),
257 override_count: 0,
258 self_call_count: 0,
259 has_listener_field,
260 has_notify_method,
261 })
262}
263
264fn scan_class_body(body: Node, src: &[u8]) -> (usize, usize, Vec<String>, bool, Vec<String>) {
266 let mut methods = 0;
267 let mut delegating = 0;
268 let mut fields = Vec::new();
269 let mut callback_fields = Vec::new();
270 let mut has_behavior = false;
271 let mut cursor = body.walk();
272 for child in body.children(&mut cursor) {
273 match child.kind() {
274 "method_definition" => {
275 let (is_behavior, is_delegating) = classify_method(child, src);
276 methods += 1;
277 has_behavior |= is_behavior;
278 delegating += usize::from(is_delegating);
279 }
280 "public_field_definition" | "property_definition" => {
281 if let Some(n) = child.child_by_field_name("name") {
282 let name = node_text(n, src).to_string();
283 if is_callback_collection_type_ts(child, src) {
284 callback_fields.push(name.clone());
285 }
286 fields.push(name);
287 }
288 }
289 _ => {}
290 }
291 }
292 (methods, delegating, fields, has_behavior, callback_fields)
293}
294
295fn classify_method(node: Node, src: &[u8]) -> (bool, bool) {
297 let mname = node
298 .child_by_field_name("name")
299 .map(|n| node_text(n, src))
300 .unwrap_or("");
301 let is_behavior = !is_accessor_name(mname) && mname != "constructor";
302 let is_delegating = node
303 .child_by_field_name("body")
304 .is_some_and(|b| check_delegating(b, src));
305 (is_behavior, is_delegating)
306}
307
308fn count_parameters(node: Node) -> usize {
309 let params = match node.child_by_field_name("parameters") {
310 Some(p) => p,
311 None => return 0,
312 };
313 let mut cursor = params.walk();
314 params
315 .children(&mut cursor)
316 .filter(|c| {
317 matches!(
318 c.kind(),
319 "required_parameter" | "optional_parameter" | "rest_parameter"
320 )
321 })
322 .count()
323}
324
325fn extract_param_types(node: Node, src: &[u8]) -> Vec<String> {
326 let params = match node.child_by_field_name("parameters") {
327 Some(p) => p,
328 None => return vec![],
329 };
330 let mut types = Vec::new();
331 let mut cursor = params.walk();
332 for child in params.children(&mut cursor) {
333 if let Some(ann) = child.child_by_field_name("type") {
334 types.push(node_text(ann, src).to_string());
335 }
336 }
337 types.sort();
338 types
339}
340
341fn max_chain_depth(node: Node) -> usize {
342 let mut max = 0;
343 walk_chain_depth(node, &mut max);
344 max
345}
346
347fn walk_chain_depth(node: Node, max: &mut usize) {
348 if node.kind() == "member_expression" {
349 let depth = measure_chain(node);
350 if depth > *max {
351 *max = depth;
352 }
353 }
354 let mut cursor = node.walk();
355 for child in node.children(&mut cursor) {
356 walk_chain_depth(child, max);
357 }
358}
359
360fn measure_chain(node: Node) -> usize {
362 let mut depth = 0;
363 let mut current = node;
364 while current.kind() == "member_expression" {
365 depth += 1;
366 if let Some(obj) = current.child_by_field_name("object") {
367 current = obj;
368 } else {
369 break;
370 }
371 }
372 depth
373}
374
375fn count_switch_arms(node: Node) -> usize {
376 let mut count = 0;
377 walk_switch_arms(node, &mut count);
378 count
379}
380
381fn walk_switch_arms(node: Node, count: &mut usize) {
382 if node.kind() == "switch_case" || node.kind() == "switch_default" {
383 *count += 1;
384 }
385 let mut cursor = node.walk();
386 for child in node.children(&mut cursor) {
387 walk_switch_arms(child, count);
388 }
389}
390
391fn collect_external_refs(node: Node, src: &[u8]) -> Vec<String> {
392 let mut refs = Vec::new();
393 walk_external_refs(node, src, &mut refs);
394 refs.sort();
395 refs.dedup();
396 refs
397}
398
399fn member_chain_root(node: Node) -> Node {
400 let mut current = node;
401 while current.kind() == "member_expression" {
402 match current.child_by_field_name("object") {
403 Some(child) => current = child,
404 None => break,
405 }
406 }
407 current
408}
409
410fn walk_external_refs(node: Node, src: &[u8], refs: &mut Vec<String>) {
411 if node.kind() == "member_expression" {
412 let root = member_chain_root(node);
413 let text = node_text(root, src);
414 if text != "this" && text != "self" && !text.is_empty() {
415 refs.push(text.to_string());
416 }
417 }
418 let mut cursor = node.walk();
419 for child in node.children(&mut cursor) {
420 walk_external_refs(child, src, refs);
421 }
422}
423
424fn single_stmt(body: Node) -> Option<Node> {
425 let mut cursor = body.walk();
426 let stmts: Vec<_> = body
427 .children(&mut cursor)
428 .filter(|c| c.kind() != "{" && c.kind() != "}")
429 .collect();
430 (stmts.len() == 1).then(|| stmts[0])
431}
432
433fn is_external_call(node: Node, src: &[u8]) -> bool {
434 node.kind() == "call_expression"
435 && node.child_by_field_name("function").is_some_and(|func| {
436 func.kind() == "member_expression"
437 && func
438 .child_by_field_name("object")
439 .is_some_and(|obj| node_text(obj, src) != "this")
440 })
441}
442
443fn check_delegating(body: Node, src: &[u8]) -> bool {
444 let Some(stmt) = single_stmt(body) else {
445 return false;
446 };
447 let expr = match stmt.kind() {
448 "return_statement" => stmt.child(1).unwrap_or(stmt),
449 "expression_statement" => stmt.child(0).unwrap_or(stmt),
450 _ => stmt,
451 };
452 is_external_call(expr, src)
453}
454
455fn count_complexity(node: Node) -> usize {
456 let mut complexity = 1;
457 walk_complexity(node, &mut complexity);
458 complexity
459}
460
461fn walk_complexity(node: Node, count: &mut usize) {
462 match node.kind() {
463 "if_statement" | "else_clause" | "for_statement" | "for_in_statement"
464 | "while_statement" | "do_statement" | "switch_case" | "catch_clause"
465 | "ternary_expression" => {
466 *count += 1;
467 }
468 "binary_expression" => {
469 let mut cursor = node.walk();
470 for child in node.children(&mut cursor) {
471 if child.kind() == "&&" || child.kind() == "||" {
472 *count += 1;
473 }
474 }
475 }
476 _ => {}
477 }
478 let mut cursor = node.walk();
479 for child in node.children(&mut cursor) {
480 walk_complexity(child, count);
481 }
482}
483
484fn extract_import(node: Node, src: &[u8]) -> Option<ImportInfo> {
485 let mut cursor = node.walk();
486 for child in node.children(&mut cursor) {
487 if child.kind() == "string" {
488 let raw = node_text(child, src);
489 let source = raw.trim_matches(|c| c == '\'' || c == '"').to_string();
490 return Some(ImportInfo {
491 source,
492 line: node.start_position().row + 1,
493 col: node.start_position().column,
494 ..Default::default()
495 });
496 }
497 }
498 None
499}
500
501fn count_comment_lines(node: Node) -> usize {
503 let mut count = 0;
504 let mut cursor = node.walk();
505 for child in node.children(&mut cursor) {
506 if child.kind() == "comment" {
507 count += child.end_position().row - child.start_position().row + 1;
508 } else if child.child_count() > 0 {
509 count += count_comment_lines(child);
510 }
511 }
512 count
513}
514
515fn collect_this_fields(body: Option<Node>, src: &[u8]) -> Vec<String> {
518 let Some(body) = body else { return vec![] };
519 let mut refs = Vec::new();
520 collect_this_refs(body, src, &mut refs);
521 refs.sort();
522 refs.dedup();
523 refs
524}
525
526fn collect_this_refs(node: Node, src: &[u8], refs: &mut Vec<String>) {
527 if node.kind() == "member_expression"
528 && let Some(obj) = node.child_by_field_name("object")
529 && node_text(obj, src) == "this"
530 && let Some(prop) = node.child_by_field_name("property")
531 {
532 refs.push(node_text(prop, src).to_string());
533 }
534 let mut cursor = node.walk();
535 for child in node.children(&mut cursor) {
536 collect_this_refs(child, src, refs);
537 }
538}
539
540fn is_accessor_name(name: &str) -> bool {
542 let lower = name.to_lowercase();
543 lower.starts_with("get") || lower.starts_with("set") || lower.starts_with("is")
544}
545
546fn extract_parent_name(node: Node, src: &[u8]) -> Option<String> {
549 let mut cursor = node.walk();
550 for child in node.children(&mut cursor) {
551 if child.kind() == "class_heritage" {
552 let mut inner = child.walk();
553 for c in child.children(&mut inner) {
554 if c.kind() == "extends_clause" {
555 let mut ec = c.walk();
557 for e in c.children(&mut ec) {
558 if e.kind() == "identifier" || e.kind() == "type_identifier" {
559 return Some(node_text(e, src).to_string());
560 }
561 }
562 }
563 }
564 }
565 }
566 None
567}
568
569fn collect_null_checks_ts(body: Option<Node>, src: &[u8]) -> Vec<String> {
571 let Some(body) = body else { return vec![] };
572 let mut fields = Vec::new();
573 walk_null_checks_ts(body, src, &mut fields);
574 fields.sort();
575 fields.dedup();
576 fields
577}
578
579fn walk_null_checks_ts(node: Node, src: &[u8], fields: &mut Vec<String>) {
580 if node.kind() == "binary_expression"
581 && let text = node_text(node, src)
582 && (text.contains("null") || text.contains("undefined"))
583 && let Some(left) = node.child_by_field_name("left")
584 && let ltext = node_text(left, src)
585 && let Some(f) = ltext.strip_prefix("this.")
586 {
587 fields.push(f.to_string());
588 }
589 let mut cursor = node.walk();
590 for child in node.children(&mut cursor) {
591 walk_null_checks_ts(child, src, fields);
592 }
593}
594
595fn extract_switch_target_ts(body: Option<Node>, src: &[u8]) -> Option<String> {
597 let body = body?;
598 find_switch_target_ts(body, src)
599}
600
601fn find_switch_target_ts(node: Node, src: &[u8]) -> Option<String> {
602 if node.kind() == "switch_statement"
603 && let Some(value) = node.child_by_field_name("value")
604 {
605 return Some(node_text(value, src).to_string());
606 }
607 let mut cursor = node.walk();
608 for child in node.children(&mut cursor) {
609 if let Some(t) = find_switch_target_ts(child, src) {
610 return Some(t);
611 }
612 }
613 None
614}
615
616fn count_optional_params_ts(node: Node, src: &[u8]) -> usize {
618 let Some(params) = node.child_by_field_name("parameters") else {
619 return 0;
620 };
621 let mut count = 0;
622 let mut cursor = params.walk();
623 for child in params.children(&mut cursor) {
624 let text = node_text(child, src);
625 if text.contains('?') || child.child_by_field_name("value").is_some() {
626 count += 1;
627 }
628 }
629 count
630}
631
632fn is_callback_collection_type_ts(field_node: Node, src: &[u8]) -> bool {
635 let Some(ty) = field_node.child_by_field_name("type") else {
636 if let Some(init) = field_node.child_by_field_name("value") {
638 let text = node_text(init, src);
639 return text == "[]" || text.contains("new Array");
640 }
641 return false;
642 };
643 let text = node_text(ty, src);
644 (text.contains("Function") && (text.contains("[]") || text.contains("Array<")))
646 || (text.contains("=>") && text.contains("[]"))
647 || text.contains("Array<(")
648}
649
650fn has_iterate_and_call_ts(body: Node, src: &[u8], cb_fields: &[String]) -> bool {
653 if cb_fields.is_empty() {
654 return false;
655 }
656 let mut cursor = body.walk();
657 for child in body.children(&mut cursor) {
658 if child.kind() == "method_definition"
659 && let Some(fn_body) = child.child_by_field_name("body")
660 {
661 for field in cb_fields {
662 let this_field = format!("this.{field}");
663 if walk_for_iterate_call_ts(fn_body, src, &this_field) {
664 return true;
665 }
666 }
667 }
668 }
669 false
670}
671
672fn walk_for_iterate_call_ts(node: Node, src: &[u8], this_field: &str) -> bool {
673 if node.kind() == "for_in_statement"
675 && node_text(node, src).contains(this_field)
676 && let Some(loop_body) = node.child_by_field_name("body")
677 && has_call_expression_ts(loop_body)
678 {
679 return true;
680 }
681 if node.kind() == "call_expression" || node.kind() == "expression_statement" {
683 let text = node_text(node, src);
684 if text.contains(this_field) && text.contains("forEach") {
685 return true;
686 }
687 }
688 let mut cursor = node.walk();
689 for child in node.children(&mut cursor) {
690 if walk_for_iterate_call_ts(child, src, this_field) {
691 return true;
692 }
693 }
694 false
695}
696
697fn collect_comments(root: Node, src: &[u8]) -> Vec<cha_core::CommentInfo> {
698 let mut comments = Vec::new();
699 let mut cursor = root.walk();
700 visit_all(root, &mut cursor, &mut |n| {
701 if n.kind().contains("comment") {
702 comments.push(cha_core::CommentInfo {
703 text: node_text(n, src).to_string(),
704 line: n.start_position().row + 1,
705 });
706 }
707 });
708 comments
709}
710
711fn has_call_expression_ts(node: Node) -> bool {
712 if node.kind() == "call_expression" {
713 return true;
714 }
715 let mut cursor = node.walk();
716 for child in node.children(&mut cursor) {
717 if has_call_expression_ts(child) {
718 return true;
719 }
720 }
721 false
722}
723
724fn cognitive_complexity_ts(node: tree_sitter::Node) -> usize {
725 let mut score = 0;
726 cc_walk_ts(node, 0, &mut score);
727 score
728}
729
730fn cc_walk_ts(node: tree_sitter::Node, nesting: usize, score: &mut usize) {
731 match node.kind() {
732 "if_statement" => {
733 *score += 1 + nesting;
734 cc_children_ts(node, nesting + 1, score);
735 return;
736 }
737 "for_statement" | "for_in_statement" | "while_statement" | "do_statement" => {
738 *score += 1 + nesting;
739 cc_children_ts(node, nesting + 1, score);
740 return;
741 }
742 "switch_statement" => {
743 *score += 1 + nesting;
744 cc_children_ts(node, nesting + 1, score);
745 return;
746 }
747 "else_clause" => {
748 *score += 1;
749 }
750 "binary_expression" => {
751 if let Some(op) = node.child_by_field_name("operator")
752 && (op.kind() == "&&" || op.kind() == "||")
753 {
754 *score += 1;
755 }
756 }
757 "catch_clause" => {
758 *score += 1 + nesting;
759 cc_children_ts(node, nesting + 1, score);
760 return;
761 }
762 "arrow_function" | "function_expression" => {
763 cc_children_ts(node, nesting + 1, score);
764 return;
765 }
766 _ => {}
767 }
768 cc_children_ts(node, nesting, score);
769}
770
771fn cc_children_ts(node: tree_sitter::Node, nesting: usize, score: &mut usize) {
772 let mut cursor = node.walk();
773 for child in node.children(&mut cursor) {
774 cc_walk_ts(child, nesting, score);
775 }
776}
777
778fn collect_calls_ts(body: Option<tree_sitter::Node>, src: &[u8]) -> Vec<String> {
779 let Some(body) = body else { return Vec::new() };
780 let mut calls = Vec::new();
781 let mut cursor = body.walk();
782 visit_all(body, &mut cursor, &mut |n| {
783 if n.kind() == "call_expression"
784 && let Some(func) = n.child(0)
785 {
786 let name = node_text(func, src).to_string();
787 if !calls.contains(&name) {
788 calls.push(name);
789 }
790 }
791 });
792 calls
793}
794
795fn visit_all<F: FnMut(Node)>(node: Node, cursor: &mut tree_sitter::TreeCursor, f: &mut F) {
796 f(node);
797 if cursor.goto_first_child() {
798 loop {
799 let child_node = cursor.node();
800 let mut child_cursor = child_node.walk();
801 visit_all(child_node, &mut child_cursor, f);
802 if !cursor.goto_next_sibling() {
803 break;
804 }
805 }
806 cursor.goto_parent();
807 }
808}