1use std::cell::RefCell;
2use std::collections::{HashMap, HashSet};
3use std::path::{Path, PathBuf};
4use std::sync::{Arc, LazyLock, RwLock};
5use std::time::SystemTime;
6
7use streaming_iterator::StreamingIterator;
8use tree_sitter::{Language, Node, Parser, Query, QueryCursor, Tree};
9
10use crate::cache_freshness::{self, FileFreshness, FreshnessVerdict};
11use crate::callgraph::resolve_module_path;
12use crate::error::AftError;
13use crate::symbol_cache_disk;
14use crate::symbols::{Range, Symbol, SymbolKind, SymbolMatch};
15
16const MAX_REEXPORT_DEPTH: usize = 10;
17
18const TS_QUERY: &str = r#"
21;; function declarations
22(function_declaration
23 name: (identifier) @fn.name) @fn.def
24
25;; function-like values assigned to const/let/var
26(lexical_declaration
27 (variable_declarator
28 name: (identifier) @arrow.name
29 value: (arrow_function) @arrow.body)) @arrow.def
30(lexical_declaration
31 (variable_declarator
32 name: (identifier) @arrow.name
33 value: (function_expression) @arrow.body)) @arrow.def
34(lexical_declaration
35 (variable_declarator
36 name: (identifier) @arrow.name
37 value: (generator_function) @arrow.body)) @arrow.def
38
39;; anonymous default exports
40(export_statement
41 value: (function_expression) @default.body) @default.def
42(export_statement
43 value: (generator_function) @default.body) @default.def
44(export_statement
45 value: (class) @default.body) @default.def
46
47;; class declarations
48(class_declaration
49 name: (type_identifier) @class.name) @class.def
50
51;; method definitions inside classes
52(class_declaration
53 name: (type_identifier) @method.class_name
54 body: (class_body
55 (method_definition
56 name: (property_identifier) @method.name) @method.def))
57
58;; interface declarations
59(interface_declaration
60 name: (type_identifier) @interface.name) @interface.def
61
62;; enum declarations
63(enum_declaration
64 name: (identifier) @enum.name) @enum.def
65
66;; type alias declarations
67(type_alias_declaration
68 name: (type_identifier) @type_alias.name) @type_alias.def
69
70;; top-level const/let variable declarations
71(lexical_declaration
72 (variable_declarator
73 name: (identifier) @var.name)) @var.def
74
75;; export statement wrappers (top-level only)
76(export_statement) @export.stmt
77"#;
78
79const JS_QUERY: &str = r#"
80;; function declarations
81(function_declaration
82 name: (identifier) @fn.name) @fn.def
83
84;; function-like values assigned to const/let/var
85(lexical_declaration
86 (variable_declarator
87 name: (identifier) @arrow.name
88 value: (arrow_function) @arrow.body)) @arrow.def
89(lexical_declaration
90 (variable_declarator
91 name: (identifier) @arrow.name
92 value: (function_expression) @arrow.body)) @arrow.def
93(lexical_declaration
94 (variable_declarator
95 name: (identifier) @arrow.name
96 value: (generator_function) @arrow.body)) @arrow.def
97
98;; anonymous default exports
99(export_statement
100 value: (function_expression) @default.body) @default.def
101(export_statement
102 value: (generator_function) @default.body) @default.def
103(export_statement
104 value: (class) @default.body) @default.def
105
106;; class declarations
107(class_declaration
108 name: (identifier) @class.name) @class.def
109
110;; method definitions inside classes
111(class_declaration
112 name: (identifier) @method.class_name
113 body: (class_body
114 (method_definition
115 name: (property_identifier) @method.name) @method.def))
116
117;; top-level const/let variable declarations
118(lexical_declaration
119 (variable_declarator
120 name: (identifier) @var.name)) @var.def
121
122;; export statement wrappers (top-level only)
123(export_statement) @export.stmt
124"#;
125
126const PY_QUERY: &str = r#"
127;; function definitions (top-level and nested)
128(function_definition
129 name: (identifier) @fn.name) @fn.def
130
131;; class definitions
132(class_definition
133 name: (identifier) @class.name) @class.def
134
135;; decorated definitions (wraps function_definition or class_definition)
136(decorated_definition
137 (decorator) @dec.decorator) @dec.def
138"#;
139
140const RS_QUERY: &str = r#"
141;; free functions (with optional visibility)
142(function_item
143 name: (identifier) @fn.name) @fn.def
144
145;; struct items
146(struct_item
147 name: (type_identifier) @struct.name) @struct.def
148
149;; enum items
150(enum_item
151 name: (type_identifier) @enum.name) @enum.def
152
153;; trait items
154(trait_item
155 name: (type_identifier) @trait.name) @trait.def
156
157;; impl blocks — capture the whole block to find methods
158(impl_item) @impl.def
159
160;; visibility modifiers on any item
161(visibility_modifier) @vis.mod
162"#;
163
164const GO_QUERY: &str = r#"
165;; function declarations
166(function_declaration
167 name: (identifier) @fn.name) @fn.def
168
169;; method declarations (with receiver)
170(method_declaration
171 name: (field_identifier) @method.name) @method.def
172
173;; type declarations (struct and interface)
174(type_declaration
175 (type_spec
176 name: (type_identifier) @type.name
177 type: (_) @type.body)) @type.def
178"#;
179
180const C_QUERY: &str = r#"
181;; function definitions
182(function_definition
183 declarator: (function_declarator
184 declarator: (identifier) @fn.name)) @fn.def
185
186;; function declarations / prototypes
187(declaration
188 declarator: (function_declarator
189 declarator: (identifier) @fn.name)) @fn.def
190
191;; struct declarations
192(struct_specifier
193 name: (type_identifier) @struct.name
194 body: (field_declaration_list)) @struct.def
195
196;; enum declarations
197(enum_specifier
198 name: (type_identifier) @enum.name
199 body: (enumerator_list)) @enum.def
200
201;; typedef aliases
202(type_definition
203 declarator: (type_identifier) @type.name) @type.def
204
205;; macros
206(preproc_def
207 name: (identifier) @macro.name) @macro.def
208
209(preproc_function_def
210 name: (identifier) @macro.name) @macro.def
211"#;
212
213const CPP_QUERY: &str = r#"
214;; free function definitions
215(function_definition
216 declarator: (function_declarator
217 declarator: (identifier) @fn.name)) @fn.def
218
219;; free function declarations
220(declaration
221 declarator: (function_declarator
222 declarator: (identifier) @fn.name)) @fn.def
223
224;; inline method definitions / declarations inside class bodies
225(function_definition
226 declarator: (function_declarator
227 declarator: (field_identifier) @method.name)) @method.def
228
229(field_declaration
230 declarator: (function_declarator
231 declarator: (field_identifier) @method.name)) @method.def
232
233;; qualified functions / methods
234(function_definition
235 declarator: (function_declarator
236 declarator: (qualified_identifier
237 scope: (_) @qual.scope
238 name: (identifier) @qual.name))) @qual.def
239
240(declaration
241 declarator: (function_declarator
242 declarator: (qualified_identifier
243 scope: (_) @qual.scope
244 name: (identifier) @qual.name))) @qual.def
245
246;; class / struct / enum / namespace declarations
247(class_specifier
248 name: (_) @class.name) @class.def
249
250(struct_specifier
251 name: (_) @struct.name) @struct.def
252
253(enum_specifier
254 name: (_) @enum.name) @enum.def
255
256(namespace_definition
257 name: (_) @namespace.name) @namespace.def
258
259;; template declarations
260(template_declaration
261 (class_specifier
262 name: (_) @template.class.name) @template.class.item) @template.class.def
263
264(template_declaration
265 (struct_specifier
266 name: (_) @template.struct.name) @template.struct.item) @template.struct.def
267
268(template_declaration
269 (function_definition
270 declarator: (function_declarator
271 declarator: (identifier) @template.fn.name)) @template.fn.item) @template.fn.def
272
273(template_declaration
274 (function_definition
275 declarator: (function_declarator
276 declarator: (qualified_identifier
277 scope: (_) @template.qual.scope
278 name: (identifier) @template.qual.name))) @template.qual.item) @template.qual.def
279"#;
280
281const ZIG_QUERY: &str = r#"
282;; functions
283(function_declaration
284 name: (identifier) @fn.name) @fn.def
285
286;; container declarations bound to const names
287(variable_declaration
288 (identifier) @struct.name
289 "="
290 (struct_declaration) @struct.body) @struct.def
291
292(variable_declaration
293 (identifier) @enum.name
294 "="
295 (enum_declaration) @enum.body) @enum.def
296
297(variable_declaration
298 (identifier) @union.name
299 "="
300 (union_declaration) @union.body) @union.def
301
302;; const declarations
303(variable_declaration
304 (identifier) @const.name) @const.def
305
306;; tests
307(test_declaration
308 (string) @test.name) @test.def
309
310(test_declaration
311 (identifier) @test.name) @test.def
312"#;
313
314const CSHARP_QUERY: &str = r#"
315;; types
316(class_declaration
317 name: (identifier) @class.name) @class.def
318
319(interface_declaration
320 name: (identifier) @interface.name) @interface.def
321
322(struct_declaration
323 name: (identifier) @struct.name) @struct.def
324
325(enum_declaration
326 name: (identifier) @enum.name) @enum.def
327
328;; members
329(method_declaration
330 name: (identifier) @method.name) @method.def
331
332(property_declaration
333 name: (identifier) @property.name) @property.def
334
335;; namespaces
336(namespace_declaration
337 name: (_) @namespace.name) @namespace.def
338
339(file_scoped_namespace_declaration
340 name: (_) @namespace.name) @namespace.def
341"#;
342
343const BASH_QUERY: &str = r#"
346;; function definitions (both `function foo()` and `foo()` styles)
347(function_definition
348 name: (word) @fn.name) @fn.def
349"#;
350
351const SOL_QUERY: &str = r#"
354;; contracts / libraries / interfaces
355(contract_declaration
356 name: (identifier) @contract.name) @contract.def
357
358(library_declaration
359 name: (identifier) @library.name) @library.def
360
361(interface_declaration
362 name: (identifier) @interface.name) @interface.def
363
364;; functions, modifiers, constructors
365(function_definition
366 name: (identifier) @fn.name) @fn.def
367
368(modifier_definition
369 name: (identifier) @modifier.name) @modifier.def
370
371(constructor_definition) @constructor.def
372
373(fallback_receive_definition) @fallback_receive.def
374
375;; events / errors
376(event_definition
377 name: (identifier) @event.name) @event.def
378
379(error_declaration
380 name: (identifier) @error.name) @error.def
381
382;; data types
383(struct_declaration
384 name: (identifier) @struct.name) @struct.def
385
386(enum_declaration
387 name: (identifier) @enum.name) @enum.def
388
389;; state variables (top-level inside a contract)
390(state_variable_declaration
391 name: (identifier) @var.name) @var.def
392"#;
393
394const SCALA_QUERY: &str = r#"
395;; classes / objects / traits
396(class_definition
397 name: (identifier) @class.name) @class.def
398(object_definition
399 name: (identifier) @object.name) @object.def
400(enum_definition
401 name: (_) @enum.name) @enum.def
402(trait_definition
403 name: (identifier) @trait.name) @trait.def
404;; methods (def)
405(function_definition
406 name: (identifier) @fn.name) @fn.def
407(function_declaration
408 name: (identifier) @fn.name) @fn.def
409;; vals / vars / type aliases
410(val_definition
411 pattern: (identifier) @val.name) @val.def
412(var_definition
413 pattern: (identifier) @var.name) @var.def
414(given_definition
415 name: (_) @given.name) @given.def
416(type_definition
417 name: (type_identifier) @type.name) @type.def
418"#;
419
420const JAVA_QUERY: &str = r#"
421;; types
422(class_declaration
423 name: (identifier) @class.name) @class.def
424(interface_declaration
425 name: (identifier) @interface.name) @interface.def
426(annotation_type_declaration
427 name: (identifier) @interface.name) @interface.def
428(enum_declaration
429 name: (identifier) @enum.name) @enum.def
430(record_declaration
431 name: (identifier) @struct.name) @struct.def
432
433;; members
434(method_declaration
435 name: (identifier) @fn.name) @fn.def
436(constructor_declaration
437 name: (identifier) @fn.name) @fn.def
438(field_declaration
439 declarator: (variable_declarator
440 name: (identifier) @var.name)) @var.def
441"#;
442
443const RUBY_QUERY: &str = r#"
444;; modules / classes
445(module
446 name: (constant) @module.name) @module.def
447(class
448 name: (constant) @class.name) @class.def
449
450;; methods
451(method
452 name: (_) @fn.name) @fn.def
453(singleton_method
454 name: (_) @fn.name) @fn.def
455
456;; constants
457(assignment
458 left: (constant) @var.name) @var.def
459"#;
460
461const KOTLIN_QUERY: &str = r#"
462;; declarations
463(class_declaration
464 (type_identifier) @class.name) @class.def
465(object_declaration
466 (type_identifier) @object.name) @object.def
467(function_declaration
468 (simple_identifier) @fn.name) @fn.def
469(property_declaration
470 (variable_declaration
471 (simple_identifier) @var.name)) @var.def
472(type_alias
473 (type_identifier) @type.name) @type.def
474"#;
475
476const SWIFT_QUERY: &str = r#"
477;; types
478(class_declaration
479 name: (type_identifier) @class.name) @class.def
480(protocol_declaration
481 name: (type_identifier) @interface.name) @interface.def
482
483;; functions and members
484(function_declaration
485 name: (simple_identifier) @fn.name) @fn.def
486(protocol_function_declaration
487 name: (simple_identifier) @fn.name) @fn.def
488(property_declaration
489 name: (pattern
490 bound_identifier: (simple_identifier) @var.name)) @var.def
491(typealias_declaration
492 name: (type_identifier) @type.name) @type.def
493"#;
494
495const PHP_QUERY: &str = r#"
496;; namespaces and types
497(namespace_definition
498 name: (namespace_name) @namespace.name) @namespace.def
499(class_declaration
500 name: (name) @class.name) @class.def
501(interface_declaration
502 name: (name) @interface.name) @interface.def
503(trait_declaration
504 name: (name) @trait.name) @trait.def
505(enum_declaration
506 name: (name) @enum.name) @enum.def
507
508;; functions and members
509(function_definition
510 name: (name) @fn.name) @fn.def
511(method_declaration
512 name: (name) @fn.name) @fn.def
513(property_declaration
514 (property_element
515 name: (variable_name (name) @var.name))) @var.def
516"#;
517
518const LUA_QUERY: &str = r#"
519;; functions
520(function_declaration
521 name: (identifier) @fn.name) @fn.def
522(function_declaration
523 name: (dot_index_expression
524 field: (identifier) @fn.name)) @fn.def
525(function_declaration
526 name: (method_index_expression
527 method: (identifier) @fn.name)) @fn.def
528
529;; locals / module tables
530(variable_declaration
531 (assignment_statement
532 (variable_list
533 name: (identifier) @var.name))) @var.def
534(variable_declaration
535 (assignment_statement
536 (variable_list
537 name: (variable) @var.name))) @var.def
538(variable_declaration
539 (variable_list
540 name: (identifier) @var.name)) @var.def
541(variable_declaration
542 (variable_list
543 name: (variable) @var.name)) @var.def
544"#;
545
546const PERL_QUERY: &str = r#"
547;; packages and subroutines
548(package_statement
549 (package_name) @package.name) @package.def
550(function_definition
551 name: (identifier) @fn.name) @fn.def
552(function_definition_without_sub
553 name: (identifier) @fn.name) @fn.def
554
555;; constants / lexical variables
556(use_constant_statement
557 constant: (identifier) @var.name) @var.def
558(variable_declaration
559 variable_name: (_) @var.name) @var.def
560"#;
561
562#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
564pub enum LangId {
565 TypeScript,
566 Tsx,
567 JavaScript,
568 Python,
569 Rust,
570 Go,
571 C,
572 Cpp,
573 Zig,
574 CSharp,
575 Bash,
576 Html,
577 Markdown,
578 Solidity,
579 Vue,
580 Json,
581 Scala,
582 Java,
583 Ruby,
584 Kotlin,
585 Swift,
586 Php,
587 Lua,
588 Perl,
589}
590
591pub fn detect_language(path: &Path) -> Option<LangId> {
593 let ext = path.extension()?.to_str()?;
594 match ext {
595 "ts" | "mts" | "cts" => Some(LangId::TypeScript),
596 "tsx" => Some(LangId::Tsx),
597 "js" | "jsx" | "mjs" | "cjs" => Some(LangId::JavaScript),
598 "py" | "pyi" => Some(LangId::Python),
599 "rs" => Some(LangId::Rust),
600 "go" => Some(LangId::Go),
601 "c" | "h" => Some(LangId::C),
602 "cc" | "cpp" | "cxx" | "hpp" | "hh" => Some(LangId::Cpp),
603 "zig" => Some(LangId::Zig),
604 "cs" => Some(LangId::CSharp),
605 "sh" | "bash" | "zsh" => Some(LangId::Bash),
606 "html" | "htm" => Some(LangId::Html),
607 "md" | "markdown" | "mdx" => Some(LangId::Markdown),
608 "sol" => Some(LangId::Solidity),
609 "vue" => Some(LangId::Vue),
610 "json" | "jsonc" => Some(LangId::Json),
611 "scala" | "sc" => Some(LangId::Scala),
612 "java" => Some(LangId::Java),
613 "rb" => Some(LangId::Ruby),
614 "kt" | "kts" => Some(LangId::Kotlin),
615 "swift" => Some(LangId::Swift),
616 "php" => Some(LangId::Php),
617 "lua" => Some(LangId::Lua),
618 "pl" | "pm" | "t" => Some(LangId::Perl),
619 _ => None,
620 }
621}
622
623pub fn grammar_for(lang: LangId) -> Language {
625 match lang {
626 LangId::TypeScript => tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(),
627 LangId::Tsx => tree_sitter_typescript::LANGUAGE_TSX.into(),
628 LangId::JavaScript => tree_sitter_javascript::LANGUAGE.into(),
629 LangId::Python => tree_sitter_python::LANGUAGE.into(),
630 LangId::Rust => tree_sitter_rust::LANGUAGE.into(),
631 LangId::Go => tree_sitter_go::LANGUAGE.into(),
632 LangId::C => tree_sitter_c::LANGUAGE.into(),
633 LangId::Cpp => tree_sitter_cpp::LANGUAGE.into(),
634 LangId::Zig => tree_sitter_zig::LANGUAGE.into(),
635 LangId::CSharp => tree_sitter_c_sharp::LANGUAGE.into(),
636 LangId::Bash => tree_sitter_bash::LANGUAGE.into(),
637 LangId::Html => tree_sitter_html::LANGUAGE.into(),
638 LangId::Markdown => tree_sitter_md::LANGUAGE.into(),
639 LangId::Solidity => tree_sitter_solidity::LANGUAGE.into(),
640 LangId::Vue => tree_sitter_vue::LANGUAGE.into(),
641 LangId::Json => tree_sitter_json::LANGUAGE.into(),
642 LangId::Scala => tree_sitter_scala::LANGUAGE.into(),
643 LangId::Java => tree_sitter_java::LANGUAGE.into(),
644 LangId::Ruby => tree_sitter_ruby::LANGUAGE.into(),
645 LangId::Kotlin => tree_sitter_kotlin_sg::LANGUAGE.into(),
646 LangId::Swift => tree_sitter_swift::LANGUAGE.into(),
647 LangId::Php => tree_sitter_php::LANGUAGE_PHP.into(),
648 LangId::Lua => tree_sitter_lua::LANGUAGE.into(),
649 LangId::Perl => tree_sitter_perl::LANGUAGE.into(),
650 }
651}
652
653fn query_for(lang: LangId) -> Option<&'static str> {
655 match lang {
656 LangId::TypeScript | LangId::Tsx => Some(TS_QUERY),
657 LangId::JavaScript => Some(JS_QUERY),
658 LangId::Python => Some(PY_QUERY),
659 LangId::Rust => Some(RS_QUERY),
660 LangId::Go => Some(GO_QUERY),
661 LangId::C => Some(C_QUERY),
662 LangId::Cpp => Some(CPP_QUERY),
663 LangId::Zig => Some(ZIG_QUERY),
664 LangId::CSharp => Some(CSHARP_QUERY),
665 LangId::Bash => Some(BASH_QUERY),
666 LangId::Html => None, LangId::Markdown => None,
668 LangId::Solidity => Some(SOL_QUERY),
669 LangId::Vue => None,
670 LangId::Json => None,
671 LangId::Scala => Some(SCALA_QUERY),
672 LangId::Java => Some(JAVA_QUERY),
673 LangId::Ruby => Some(RUBY_QUERY),
674 LangId::Kotlin => Some(KOTLIN_QUERY),
675 LangId::Swift => Some(SWIFT_QUERY),
676 LangId::Php => Some(PHP_QUERY),
677 LangId::Lua => Some(LUA_QUERY),
678 LangId::Perl => Some(PERL_QUERY),
679 }
680}
681
682static TS_QUERY_CACHE: LazyLock<Result<Query, String>> =
683 LazyLock::new(|| compile_query(LangId::TypeScript));
684static TSX_QUERY_CACHE: LazyLock<Result<Query, String>> =
685 LazyLock::new(|| compile_query(LangId::Tsx));
686static JS_QUERY_CACHE: LazyLock<Result<Query, String>> =
687 LazyLock::new(|| compile_query(LangId::JavaScript));
688static PY_QUERY_CACHE: LazyLock<Result<Query, String>> =
689 LazyLock::new(|| compile_query(LangId::Python));
690static RS_QUERY_CACHE: LazyLock<Result<Query, String>> =
691 LazyLock::new(|| compile_query(LangId::Rust));
692static GO_QUERY_CACHE: LazyLock<Result<Query, String>> =
693 LazyLock::new(|| compile_query(LangId::Go));
694static C_QUERY_CACHE: LazyLock<Result<Query, String>> = LazyLock::new(|| compile_query(LangId::C));
695static CPP_QUERY_CACHE: LazyLock<Result<Query, String>> =
696 LazyLock::new(|| compile_query(LangId::Cpp));
697static ZIG_QUERY_CACHE: LazyLock<Result<Query, String>> =
698 LazyLock::new(|| compile_query(LangId::Zig));
699static CSHARP_QUERY_CACHE: LazyLock<Result<Query, String>> =
700 LazyLock::new(|| compile_query(LangId::CSharp));
701static BASH_QUERY_CACHE: LazyLock<Result<Query, String>> =
702 LazyLock::new(|| compile_query(LangId::Bash));
703static SOL_QUERY_CACHE: LazyLock<Result<Query, String>> =
704 LazyLock::new(|| compile_query(LangId::Solidity));
705static SCALA_QUERY_CACHE: LazyLock<Result<Query, String>> =
706 LazyLock::new(|| compile_query(LangId::Scala));
707static JAVA_QUERY_CACHE: LazyLock<Result<Query, String>> =
708 LazyLock::new(|| compile_query(LangId::Java));
709static RUBY_QUERY_CACHE: LazyLock<Result<Query, String>> =
710 LazyLock::new(|| compile_query(LangId::Ruby));
711static KOTLIN_QUERY_CACHE: LazyLock<Result<Query, String>> =
712 LazyLock::new(|| compile_query(LangId::Kotlin));
713static SWIFT_QUERY_CACHE: LazyLock<Result<Query, String>> =
714 LazyLock::new(|| compile_query(LangId::Swift));
715static PHP_QUERY_CACHE: LazyLock<Result<Query, String>> =
716 LazyLock::new(|| compile_query(LangId::Php));
717static LUA_QUERY_CACHE: LazyLock<Result<Query, String>> =
718 LazyLock::new(|| compile_query(LangId::Lua));
719static PERL_QUERY_CACHE: LazyLock<Result<Query, String>> =
720 LazyLock::new(|| compile_query(LangId::Perl));
721
722fn compile_query(lang: LangId) -> Result<Query, String> {
723 let query_src = query_for(lang).ok_or_else(|| format!("missing query for {lang:?}"))?;
724 let grammar = grammar_for(lang);
725 Query::new(&grammar, query_src)
726 .map_err(|error| format!("query compile error for {lang:?}: {error}"))
727}
728
729fn cached_query_for(lang: LangId) -> Result<Option<&'static Query>, AftError> {
730 let query = match lang {
731 LangId::TypeScript => Some(&*TS_QUERY_CACHE),
732 LangId::Tsx => Some(&*TSX_QUERY_CACHE),
733 LangId::JavaScript => Some(&*JS_QUERY_CACHE),
734 LangId::Python => Some(&*PY_QUERY_CACHE),
735 LangId::Rust => Some(&*RS_QUERY_CACHE),
736 LangId::Go => Some(&*GO_QUERY_CACHE),
737 LangId::C => Some(&*C_QUERY_CACHE),
738 LangId::Cpp => Some(&*CPP_QUERY_CACHE),
739 LangId::Zig => Some(&*ZIG_QUERY_CACHE),
740 LangId::CSharp => Some(&*CSHARP_QUERY_CACHE),
741 LangId::Bash => Some(&*BASH_QUERY_CACHE),
742 LangId::Solidity => Some(&*SOL_QUERY_CACHE),
743 LangId::Scala => Some(&*SCALA_QUERY_CACHE),
744 LangId::Java => Some(&*JAVA_QUERY_CACHE),
745 LangId::Ruby => Some(&*RUBY_QUERY_CACHE),
746 LangId::Kotlin => Some(&*KOTLIN_QUERY_CACHE),
747 LangId::Swift => Some(&*SWIFT_QUERY_CACHE),
748 LangId::Php => Some(&*PHP_QUERY_CACHE),
749 LangId::Lua => Some(&*LUA_QUERY_CACHE),
750 LangId::Perl => Some(&*PERL_QUERY_CACHE),
751 LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => None,
752 };
753
754 query
755 .map(|result| {
756 result.as_ref().map_err(|message| AftError::ParseError {
757 message: message.clone(),
758 })
759 })
760 .transpose()
761}
762
763struct CachedTree {
765 mtime: SystemTime,
766 size: u64,
767 content_hash: blake3::Hash,
768 tree: Tree,
769}
770
771#[derive(Clone)]
773struct CachedSymbols {
774 mtime: SystemTime,
775 size: u64,
776 content_hash: blake3::Hash,
777 symbols: Vec<Symbol>,
778}
779
780fn content_hash_for_source(source: &str) -> blake3::Hash {
781 if source.len() as u64 > cache_freshness::CONTENT_HASH_SIZE_CAP {
782 cache_freshness::zero_hash()
783 } else {
784 cache_freshness::hash_bytes(source.as_bytes())
785 }
786}
787
788fn cached_file_is_fresh(
789 path: &Path,
790 cached_mtime: SystemTime,
791 cached_size: u64,
792 cached_content_hash: blake3::Hash,
793 fallback_mtime: SystemTime,
794) -> bool {
795 let Ok(metadata) = std::fs::metadata(path) else {
796 return false;
797 };
798 let current_size = metadata.len();
799 if current_size != cached_size {
800 return false;
801 }
802
803 let current_mtime = metadata.modified().unwrap_or(fallback_mtime);
804 if current_size > cache_freshness::CONTENT_HASH_SIZE_CAP {
805 return current_mtime == cached_mtime;
806 }
807
808 matches!(
809 cache_freshness::hash_file_if_small(path, current_size),
810 Ok(Some(hash)) if hash == cached_content_hash
811 )
812}
813
814#[derive(Clone, Default)]
817pub struct SymbolCache {
818 entries: HashMap<PathBuf, CachedSymbols>,
819 generation: u64,
820 project_root: Option<PathBuf>,
821}
822
823pub type SharedSymbolCache = Arc<RwLock<SymbolCache>>;
824
825impl SymbolCache {
826 pub fn new() -> Self {
827 Self {
828 entries: HashMap::new(),
829 generation: 0,
830 project_root: None,
831 }
832 }
833
834 pub fn set_project_root(&mut self, project_root: PathBuf) {
836 debug_assert!(project_root.is_absolute());
837 self.project_root = Some(project_root);
838 }
839
840 pub fn set_project_root_for_generation(
843 &mut self,
844 generation: u64,
845 project_root: PathBuf,
846 ) -> bool {
847 if self.generation != generation {
848 return false;
849 }
850 self.set_project_root(project_root);
851 true
852 }
853
854 pub fn insert(
856 &mut self,
857 path: PathBuf,
858 mtime: SystemTime,
859 size: u64,
860 content_hash: blake3::Hash,
861 symbols: Vec<Symbol>,
862 ) {
863 self.entries.insert(
864 path,
865 CachedSymbols {
866 mtime,
867 size,
868 content_hash,
869 symbols,
870 },
871 );
872 }
873
874 pub fn insert_for_generation(
876 &mut self,
877 generation: u64,
878 path: PathBuf,
879 mtime: SystemTime,
880 size: u64,
881 content_hash: blake3::Hash,
882 symbols: Vec<Symbol>,
883 ) -> bool {
884 if self.generation != generation {
885 return false;
886 }
887 self.insert(path, mtime, size, content_hash, symbols);
888 true
889 }
890
891 pub fn get(&self, path: &Path, mtime: SystemTime) -> Option<Vec<Symbol>> {
893 self.entries.get(path).and_then(|cached| {
894 cached_file_is_fresh(path, cached.mtime, cached.size, cached.content_hash, mtime)
895 .then(|| cached.symbols.clone())
896 })
897 }
898
899 pub fn symbol_count_if_metadata_matches(
905 &self,
906 path: &Path,
907 mtime: SystemTime,
908 size: u64,
909 ) -> Option<usize> {
910 self.entries.get(path).and_then(|cached| {
911 (cached.mtime == mtime && cached.size == size).then_some(cached.symbols.len())
912 })
913 }
914
915 pub fn contains_path_with_mtime(&self, path: &Path, mtime: SystemTime) -> bool {
917 self.entries
918 .get(path)
919 .is_some_and(|cached| cached.mtime == mtime)
920 }
921
922 pub fn load_from_disk(
924 &mut self,
925 storage_dir: &Path,
926 project_key: &str,
927 current_root: &Path,
928 ) -> usize {
929 debug_assert!(current_root.is_absolute());
930 let Some(cache) = symbol_cache_disk::read_from_disk(storage_dir, project_key) else {
931 return 0;
932 };
933
934 self.project_root = Some(current_root.to_path_buf());
935 self.entries.clear();
936 let mut loaded = 0usize;
937
938 for entry in cache.entries {
939 let Some(path) =
940 crate::search_index::cached_path_under_root(current_root, &entry.relative_path)
941 else {
942 continue;
943 };
944 let cached_freshness = FileFreshness {
945 mtime: entry.mtime,
946 size: entry.size,
947 content_hash: entry.content_hash,
948 };
949 let mtime = match cache_freshness::verify_file(&path, &cached_freshness) {
950 FreshnessVerdict::HotFresh => entry.mtime,
951 FreshnessVerdict::ContentFresh { new_mtime, .. } => new_mtime,
952 FreshnessVerdict::Stale | FreshnessVerdict::Deleted => continue,
953 };
954
955 self.entries.insert(
956 path,
957 CachedSymbols {
958 mtime,
959 size: entry.size,
960 content_hash: entry.content_hash,
961 symbols: entry.symbols,
962 },
963 );
964 loaded += 1;
965 }
966
967 loaded
968 }
969
970 pub fn load_from_disk_for_generation(
973 &mut self,
974 generation: u64,
975 storage_dir: &Path,
976 project_key: &str,
977 current_root: &Path,
978 ) -> usize {
979 if self.generation != generation {
980 return 0;
981 }
982 self.load_from_disk(storage_dir, project_key, current_root)
983 }
984
985 pub fn invalidate(&mut self, path: &Path) {
987 self.entries.remove(path);
988 }
989
990 pub fn reset(&mut self) -> u64 {
992 self.entries.clear();
993 self.project_root = None;
994 self.generation = self.generation.wrapping_add(1);
995 self.generation
996 }
997
998 pub fn generation(&self) -> u64 {
1000 self.generation
1001 }
1002
1003 pub fn contains_key(&self, path: &Path) -> bool {
1005 self.entries.contains_key(path)
1006 }
1007
1008 pub fn len(&self) -> usize {
1010 self.entries.len()
1011 }
1012
1013 pub(crate) fn project_root(&self) -> Option<PathBuf> {
1014 self.project_root.clone()
1015 }
1016
1017 pub(crate) fn disk_entries(
1018 &self,
1019 ) -> Vec<(&PathBuf, SystemTime, u64, blake3::Hash, &Vec<Symbol>)> {
1020 self.entries
1021 .iter()
1022 .filter_map(|(path, cached)| {
1023 if cached.symbols.is_empty() {
1024 return None;
1025 }
1026 Some((
1027 path,
1028 cached.mtime,
1029 cached.size,
1030 cached.content_hash,
1031 &cached.symbols,
1032 ))
1033 })
1034 .collect()
1035 }
1036}
1037
1038pub struct FileParser {
1041 cache: HashMap<PathBuf, CachedTree>,
1042 parsers: HashMap<LangId, Parser>,
1043 symbol_cache: SharedSymbolCache,
1044 symbol_cache_generation: Option<u64>,
1045}
1046
1047impl FileParser {
1048 pub fn new() -> Self {
1050 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
1051 }
1052
1053 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
1055 Self::with_symbol_cache_generation(symbol_cache, None)
1056 }
1057
1058 pub fn with_symbol_cache_generation(
1060 symbol_cache: SharedSymbolCache,
1061 symbol_cache_generation: Option<u64>,
1062 ) -> Self {
1063 Self {
1064 cache: HashMap::new(),
1065 parsers: HashMap::new(),
1066 symbol_cache,
1067 symbol_cache_generation,
1068 }
1069 }
1070
1071 fn parser_for(&mut self, lang: LangId) -> Result<&mut Parser, AftError> {
1072 use std::collections::hash_map::Entry;
1073
1074 match self.parsers.entry(lang) {
1075 Entry::Occupied(entry) => Ok(entry.into_mut()),
1076 Entry::Vacant(entry) => {
1077 let grammar = grammar_for(lang);
1078 let mut parser = Parser::new();
1079 parser.set_language(&grammar).map_err(|e| {
1080 crate::slog_error!("grammar init failed for {:?}: {}", lang, e);
1081 AftError::ParseError {
1082 message: format!("grammar init failed for {:?}: {}", lang, e),
1083 }
1084 })?;
1085 Ok(entry.insert(parser))
1086 }
1087 }
1088 }
1089
1090 pub fn symbol_cache_len(&self) -> usize {
1092 self.symbol_cache
1093 .read()
1094 .map(|cache| cache.len())
1095 .unwrap_or(0)
1096 }
1097
1098 pub fn symbol_cache(&self) -> SharedSymbolCache {
1100 Arc::clone(&self.symbol_cache)
1101 }
1102
1103 pub fn parse(&mut self, path: &Path) -> Result<(&Tree, LangId), AftError> {
1106 let lang = detect_language(path).ok_or_else(|| AftError::InvalidRequest {
1107 message: format!(
1108 "unsupported file extension: {}",
1109 path.extension()
1110 .and_then(|e| e.to_str())
1111 .unwrap_or("<none>")
1112 ),
1113 })?;
1114
1115 let canon = path.to_path_buf();
1116 let current_mtime = std::fs::metadata(path)
1117 .and_then(|m| m.modified())
1118 .map_err(|e| AftError::FileNotFound {
1119 path: format!("{}: {}", path.display(), e),
1120 })?;
1121
1122 let needs_reparse = match self.cache.get(&canon) {
1126 Some(cached) => !cached_file_is_fresh(
1127 path,
1128 cached.mtime,
1129 cached.size,
1130 cached.content_hash,
1131 current_mtime,
1132 ),
1133 None => true,
1134 };
1135
1136 if needs_reparse {
1137 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
1138 path: format!("{}: {}", path.display(), e),
1139 })?;
1140
1141 let tree = self.parser_for(lang)?.parse(&source, None).ok_or_else(|| {
1142 crate::slog_error!("parse failed for {}", path.display());
1143 AftError::ParseError {
1144 message: format!("tree-sitter parse returned None for {}", path.display()),
1145 }
1146 })?;
1147
1148 self.cache.insert(
1149 canon.clone(),
1150 CachedTree {
1151 mtime: current_mtime,
1152 size: source.len() as u64,
1153 content_hash: content_hash_for_source(&source),
1154 tree,
1155 },
1156 );
1157 }
1158
1159 let cached = self.cache.get(&canon).ok_or_else(|| AftError::ParseError {
1160 message: format!("parser cache missing entry for {}", path.display()),
1161 })?;
1162 Ok((&cached.tree, lang))
1163 }
1164
1165 pub fn parse_cloned(&mut self, path: &Path) -> Result<(Tree, LangId), AftError> {
1170 let (tree, lang) = self.parse(path)?;
1171 Ok((tree.clone(), lang))
1172 }
1173
1174 pub fn extract_symbols(&mut self, path: &Path) -> Result<Vec<Symbol>, AftError> {
1178 let canon = path.to_path_buf();
1179 let current_mtime = std::fs::metadata(path)
1180 .and_then(|m| m.modified())
1181 .map_err(|e| AftError::FileNotFound {
1182 path: format!("{}: {}", path.display(), e),
1183 })?;
1184
1185 if let Some(symbols) = self
1187 .symbol_cache
1188 .read()
1189 .map_err(|_| AftError::ParseError {
1190 message: "symbol cache lock poisoned".to_string(),
1191 })?
1192 .get(&canon, current_mtime)
1193 {
1194 return Ok(symbols);
1195 }
1196
1197 let source = std::fs::read_to_string(path).map_err(|e| AftError::FileNotFound {
1198 path: format!("{}: {}", path.display(), e),
1199 })?;
1200 let size = source.len() as u64;
1201 let content_hash = content_hash_for_source(&source);
1202
1203 let symbols = {
1204 let (tree, lang) = self.parse(path)?;
1205 extract_symbols_from_tree(&source, tree, lang)?
1206 };
1207
1208 let mut symbol_cache = self
1210 .symbol_cache
1211 .write()
1212 .map_err(|_| AftError::ParseError {
1213 message: "symbol cache lock poisoned".to_string(),
1214 })?;
1215 if let Some(generation) = self.symbol_cache_generation {
1216 symbol_cache.insert_for_generation(
1217 generation,
1218 canon,
1219 current_mtime,
1220 size,
1221 content_hash,
1222 symbols.clone(),
1223 );
1224 } else {
1225 symbol_cache.insert(canon, current_mtime, size, content_hash, symbols.clone());
1226 }
1227
1228 Ok(symbols)
1229 }
1230
1231 pub fn invalidate_symbols(&mut self, path: &Path) {
1233 if let Ok(mut symbol_cache) = self.symbol_cache.write() {
1234 symbol_cache.invalidate(path);
1235 }
1236 self.cache.remove(path);
1237 }
1238}
1239
1240pub fn extract_symbols_from_tree(
1245 source: &str,
1246 tree: &Tree,
1247 lang: LangId,
1248) -> Result<Vec<Symbol>, AftError> {
1249 let root = tree.root_node();
1250
1251 if lang == LangId::Html {
1252 return extract_html_symbols(source, &root);
1253 }
1254 if lang == LangId::Markdown {
1255 return extract_md_symbols(source, &root);
1256 }
1257 if lang == LangId::Vue {
1258 return extract_vue_symbols(source, &root);
1259 }
1260 if lang == LangId::Json {
1261 return extract_json_symbols(source, &root);
1262 }
1263
1264 let query = cached_query_for(lang)?.ok_or_else(|| AftError::InvalidRequest {
1265 message: format!("no query patterns implemented for {:?} yet", lang),
1266 })?;
1267
1268 match lang {
1269 LangId::TypeScript | LangId::Tsx => extract_ts_symbols(source, &root, query),
1270 LangId::JavaScript => extract_js_symbols(source, &root, query),
1271 LangId::Python => extract_py_symbols(source, &root, query),
1272 LangId::Rust => extract_rs_symbols(source, &root, query),
1273 LangId::Go => extract_go_symbols(source, &root, query),
1274 LangId::C => extract_c_symbols(source, &root, query),
1275 LangId::Cpp => extract_cpp_symbols(source, &root, query),
1276 LangId::Zig => extract_zig_symbols(source, &root, query),
1277 LangId::CSharp => extract_csharp_symbols(source, &root, query),
1278 LangId::Bash => extract_bash_symbols(source, &root, query),
1279 LangId::Solidity => extract_solidity_symbols(source, &root, query),
1280 LangId::Scala => extract_scala_symbols(source, &root, query),
1281 LangId::Java => extract_java_symbols(source, &root, query),
1282 LangId::Ruby => extract_ruby_symbols(source, &root, query),
1283 LangId::Kotlin => extract_kotlin_symbols(source, &root, query),
1284 LangId::Swift => extract_swift_symbols(source, &root, query),
1285 LangId::Php => extract_php_symbols(source, &root, query),
1286 LangId::Lua => extract_lua_symbols(source, &root, query),
1287 LangId::Perl => extract_perl_symbols(source, &root, query),
1288 LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => {
1289 unreachable!("handled before query lookup")
1290 }
1291 }
1292}
1293
1294pub(crate) fn node_range(node: &Node) -> Range {
1296 let start = node.start_position();
1297 let end = node.end_position();
1298 Range {
1299 start_line: start.row as u32,
1300 start_col: start.column as u32,
1301 end_line: end.row as u32,
1302 end_col: end.column as u32,
1303 }
1304}
1305
1306pub(crate) fn node_range_with_decorators(node: &Node, source: &str, lang: LangId) -> Range {
1312 if matches!(lang, LangId::Python) {
1313 if let Some(parent) = node.parent() {
1314 if parent.kind() == "decorated_definition" {
1315 return node_range(&parent);
1316 }
1317 }
1318 }
1319
1320 if matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
1328 if let Some(parent) = node.parent() {
1329 if parent.kind() == "export_statement" {
1330 return node_range_with_decorators_inner(&parent, source, lang);
1331 }
1332 }
1333 }
1334
1335 node_range_with_decorators_inner(node, source, lang)
1336}
1337
1338fn node_range_with_decorators_inner(node: &Node, source: &str, lang: LangId) -> Range {
1342 let mut range = node_range(node);
1343
1344 let mut current = *node;
1345 while let Some(prev) = current.prev_sibling() {
1346 let kind = prev.kind();
1347 let should_include = match lang {
1348 LangId::Rust => {
1349 kind == "attribute_item"
1351 || (kind == "line_comment"
1353 && node_text(source, &prev).starts_with("///"))
1354 || (kind == "block_comment"
1356 && node_text(source, &prev).starts_with("/**"))
1357 }
1358 LangId::TypeScript | LangId::Tsx | LangId::JavaScript => {
1359 kind == "decorator"
1361 || (kind == "comment"
1363 && node_text(source, &prev).starts_with("/**"))
1364 }
1365 LangId::Go | LangId::C | LangId::Cpp | LangId::Zig | LangId::CSharp | LangId::Bash => {
1366 kind == "comment" && is_adjacent_line(&prev, ¤t, source)
1368 }
1369 LangId::Solidity
1370 | LangId::Scala
1371 | LangId::Java
1372 | LangId::Kotlin
1373 | LangId::Swift
1374 | LangId::Php => {
1375 let text = node_text(source, &prev);
1377 (kind == "comment" || kind == "line_comment" || kind == "block_comment")
1378 && (text.starts_with("///") || text.starts_with("/**"))
1379 && is_adjacent_line(&prev, ¤t, source)
1380 }
1381 LangId::Ruby | LangId::Lua => {
1382 let text = node_text(source, &prev);
1384 kind == "comment"
1385 && (text.starts_with('#') || text.starts_with("---"))
1386 && is_adjacent_line(&prev, ¤t, source)
1387 }
1388 LangId::Perl => false,
1389 LangId::Python => {
1390 false
1392 }
1393 LangId::Html | LangId::Markdown | LangId::Vue | LangId::Json => false,
1394 };
1395
1396 if should_include {
1397 range.start_line = prev.start_position().row as u32;
1398 range.start_col = prev.start_position().column as u32;
1399 current = prev;
1400 } else {
1401 break;
1402 }
1403 }
1404
1405 range
1406}
1407
1408fn is_adjacent_line(upper: &Node, lower: &Node, source: &str) -> bool {
1410 let upper_end = upper.end_position().row;
1411 let lower_start = lower.start_position().row;
1412
1413 if lower_start == 0 || lower_start <= upper_end {
1414 return true;
1415 }
1416
1417 let lines: Vec<&str> = source.lines().collect();
1419 for row in (upper_end + 1)..lower_start {
1420 if row < lines.len() && lines[row].trim().is_empty() {
1421 return false;
1422 }
1423 }
1424 true
1425}
1426
1427pub(crate) fn node_text<'a>(source: &'a str, node: &Node) -> &'a str {
1429 &source[node.byte_range()]
1430}
1431
1432fn lexical_declaration_has_function_value(node: &Node) -> bool {
1433 let mut cursor = node.walk();
1434 if !cursor.goto_first_child() {
1435 return false;
1436 }
1437
1438 loop {
1439 let child = cursor.node();
1440 if matches!(
1441 child.kind(),
1442 "arrow_function" | "function_expression" | "generator_function"
1443 ) {
1444 return true;
1445 }
1446
1447 if lexical_declaration_has_function_value(&child) {
1448 return true;
1449 }
1450
1451 if !cursor.goto_next_sibling() {
1452 break;
1453 }
1454 }
1455
1456 false
1457}
1458
1459fn collect_export_ranges(source: &str, root: &Node, query: &Query) -> Vec<std::ops::Range<usize>> {
1461 let export_idx = query
1462 .capture_names()
1463 .iter()
1464 .position(|n| *n == "export.stmt");
1465 let export_idx = match export_idx {
1466 Some(i) => i as u32,
1467 None => return vec![],
1468 };
1469
1470 let mut cursor = QueryCursor::new();
1471 let mut ranges = Vec::new();
1472 let mut matches = cursor.matches(query, *root, source.as_bytes());
1473
1474 while let Some(m) = {
1475 matches.advance();
1476 matches.get()
1477 } {
1478 for cap in m.captures {
1479 if cap.index == export_idx {
1480 ranges.push(cap.node.byte_range());
1481 }
1482 }
1483 }
1484 ranges
1485}
1486
1487fn is_exported(node: &Node, export_ranges: &[std::ops::Range<usize>]) -> bool {
1489 let r = node.byte_range();
1490 export_ranges
1491 .iter()
1492 .any(|er| er.start <= r.start && r.end <= er.end)
1493}
1494
1495fn collect_exported_symbol_names(source: &str, root: &Node) -> HashSet<String> {
1496 let mut exported = HashSet::new();
1497 collect_exported_symbol_names_inner(source, root, &mut exported);
1498 exported
1499}
1500
1501fn collect_exported_symbol_names_inner(source: &str, node: &Node, exported: &mut HashSet<String>) {
1502 if node.kind() == "export_statement" {
1503 collect_names_from_export_statement(source, node, exported);
1504 }
1505
1506 let mut cursor = node.walk();
1507 if !cursor.goto_first_child() {
1508 return;
1509 }
1510
1511 loop {
1512 let child = cursor.node();
1513 collect_exported_symbol_names_inner(source, &child, exported);
1514 if !cursor.goto_next_sibling() {
1515 break;
1516 }
1517 }
1518}
1519
1520fn collect_names_from_export_statement(source: &str, node: &Node, exported: &mut HashSet<String>) {
1521 let mut cursor = node.walk();
1522 if !cursor.goto_first_child() {
1523 return;
1524 }
1525
1526 let mut saw_default = false;
1527 loop {
1528 let child = cursor.node();
1529 match child.kind() {
1530 "default" => saw_default = true,
1531 "export_clause" => collect_names_from_export_clause(source, &child, exported),
1532 "identifier" | "type_identifier" | "property_identifier" if saw_default => {
1533 exported.insert(node_text(source, &child).to_string());
1534 return;
1535 }
1536 _ => {}
1537 }
1538 if !cursor.goto_next_sibling() {
1539 break;
1540 }
1541 }
1542}
1543
1544fn collect_names_from_export_clause(source: &str, node: &Node, exported: &mut HashSet<String>) {
1545 let mut cursor = node.walk();
1546 if !cursor.goto_first_child() {
1547 return;
1548 }
1549
1550 loop {
1551 let child = cursor.node();
1552 if child.kind() == "export_specifier" {
1553 if let Some(exported_name) = last_identifier_text(source, &child) {
1554 exported.insert(exported_name);
1555 }
1556 }
1557 if !cursor.goto_next_sibling() {
1558 break;
1559 }
1560 }
1561}
1562
1563fn last_identifier_text(source: &str, node: &Node) -> Option<String> {
1564 let mut cursor = node.walk();
1565 if !cursor.goto_first_child() {
1566 return None;
1567 }
1568
1569 let mut last = None;
1570 loop {
1571 let child = cursor.node();
1572 if matches!(
1573 child.kind(),
1574 "identifier"
1575 | "type_identifier"
1576 | "property_identifier"
1577 | "shorthand_property_identifier"
1578 ) {
1579 last = Some(node_text(source, &child).to_string());
1580 }
1581 if !cursor.goto_next_sibling() {
1582 break;
1583 }
1584 }
1585 last
1586}
1587
1588fn mark_named_exports(symbols: &mut [Symbol], exported_names: &HashSet<String>) {
1589 for symbol in symbols {
1590 if symbol.scope_chain.is_empty()
1591 && symbol.parent.is_none()
1592 && exported_names.contains(&symbol.name)
1593 {
1594 symbol.exported = true;
1595 }
1596 }
1597}
1598
1599fn extract_signature(source: &str, node: &Node) -> String {
1601 let text = node_text(source, node);
1602 let first_line = text.lines().next().unwrap_or(text);
1603 let trimmed = first_line.trim_end();
1605 let trimmed = trimmed.strip_suffix('{').unwrap_or(trimmed).trim_end();
1606 trimmed.to_string()
1607}
1608
1609fn push_default_export_symbol(
1610 symbols: &mut Vec<Symbol>,
1611 source: &str,
1612 lang: LangId,
1613 body_node: Node,
1614 def_node: Node,
1615) {
1616 let kind = if body_node.kind() == "class" {
1617 SymbolKind::Class
1618 } else {
1619 SymbolKind::Function
1620 };
1621
1622 symbols.push(Symbol {
1623 name: "default".to_string(),
1624 kind,
1625 range: node_range_with_decorators(&def_node, source, lang),
1626 signature: Some(extract_signature(source, &def_node)),
1627 scope_chain: vec![],
1628 exported: true,
1629 parent: None,
1630 });
1631}
1632
1633fn extract_ts_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1635 let lang = LangId::TypeScript;
1636 let capture_names = query.capture_names();
1637
1638 let export_ranges = collect_export_ranges(source, root, query);
1639 let exported_names = collect_exported_symbol_names(source, root);
1640
1641 let mut symbols = Vec::new();
1642 let mut cursor = QueryCursor::new();
1643 let mut matches = cursor.matches(query, *root, source.as_bytes());
1644
1645 while let Some(m) = {
1646 matches.advance();
1647 matches.get()
1648 } {
1649 let mut fn_name_node = None;
1651 let mut fn_def_node = None;
1652 let mut arrow_name_node = None;
1653 let mut arrow_def_node = None;
1654 let mut class_name_node = None;
1655 let mut class_def_node = None;
1656 let mut method_class_name_node = None;
1657 let mut method_name_node = None;
1658 let mut method_def_node = None;
1659 let mut interface_name_node = None;
1660 let mut interface_def_node = None;
1661 let mut enum_name_node = None;
1662 let mut enum_def_node = None;
1663 let mut type_alias_name_node = None;
1664 let mut type_alias_def_node = None;
1665 let mut var_name_node = None;
1666 let mut var_def_node = None;
1667 let mut default_body_node = None;
1668 let mut default_def_node = None;
1669
1670 for cap in m.captures {
1671 let Some(&name) = capture_names.get(cap.index as usize) else {
1672 continue;
1673 };
1674 match name {
1675 "fn.name" => fn_name_node = Some(cap.node),
1676 "fn.def" => fn_def_node = Some(cap.node),
1677 "arrow.name" => arrow_name_node = Some(cap.node),
1678 "arrow.def" => arrow_def_node = Some(cap.node),
1679 "class.name" => class_name_node = Some(cap.node),
1680 "class.def" => class_def_node = Some(cap.node),
1681 "method.class_name" => method_class_name_node = Some(cap.node),
1682 "method.name" => method_name_node = Some(cap.node),
1683 "method.def" => method_def_node = Some(cap.node),
1684 "interface.name" => interface_name_node = Some(cap.node),
1685 "interface.def" => interface_def_node = Some(cap.node),
1686 "enum.name" => enum_name_node = Some(cap.node),
1687 "enum.def" => enum_def_node = Some(cap.node),
1688 "type_alias.name" => type_alias_name_node = Some(cap.node),
1689 "type_alias.def" => type_alias_def_node = Some(cap.node),
1690 "var.name" => var_name_node = Some(cap.node),
1691 "var.def" => var_def_node = Some(cap.node),
1692 "default.body" => default_body_node = Some(cap.node),
1693 "default.def" => default_def_node = Some(cap.node),
1694 _ => {}
1696 }
1697 }
1698
1699 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1701 symbols.push(Symbol {
1702 name: node_text(source, &name_node).to_string(),
1703 kind: SymbolKind::Function,
1704 range: node_range_with_decorators(&def_node, source, lang),
1705 signature: Some(extract_signature(source, &def_node)),
1706 scope_chain: vec![],
1707 exported: is_exported(&def_node, &export_ranges),
1708 parent: None,
1709 });
1710 }
1711
1712 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1714 symbols.push(Symbol {
1715 name: node_text(source, &name_node).to_string(),
1716 kind: SymbolKind::Function,
1717 range: node_range_with_decorators(&def_node, source, lang),
1718 signature: Some(extract_signature(source, &def_node)),
1719 scope_chain: vec![],
1720 exported: is_exported(&def_node, &export_ranges),
1721 parent: None,
1722 });
1723 }
1724
1725 if let (Some(body_node), Some(def_node)) = (default_body_node, default_def_node) {
1727 push_default_export_symbol(&mut symbols, source, lang, body_node, def_node);
1728 }
1729
1730 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1732 symbols.push(Symbol {
1733 name: node_text(source, &name_node).to_string(),
1734 kind: SymbolKind::Class,
1735 range: node_range_with_decorators(&def_node, source, lang),
1736 signature: Some(extract_signature(source, &def_node)),
1737 scope_chain: vec![],
1738 exported: is_exported(&def_node, &export_ranges),
1739 parent: None,
1740 });
1741 }
1742
1743 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1745 (method_class_name_node, method_name_node, method_def_node)
1746 {
1747 let class_name = node_text(source, &class_name_node).to_string();
1748 symbols.push(Symbol {
1749 name: node_text(source, &name_node).to_string(),
1750 kind: SymbolKind::Method,
1751 range: node_range_with_decorators(&def_node, source, lang),
1752 signature: Some(extract_signature(source, &def_node)),
1753 scope_chain: vec![class_name.clone()],
1754 exported: false, parent: Some(class_name),
1756 });
1757 }
1758
1759 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
1761 symbols.push(Symbol {
1762 name: node_text(source, &name_node).to_string(),
1763 kind: SymbolKind::Interface,
1764 range: node_range_with_decorators(&def_node, source, lang),
1765 signature: Some(extract_signature(source, &def_node)),
1766 scope_chain: vec![],
1767 exported: is_exported(&def_node, &export_ranges),
1768 parent: None,
1769 });
1770 }
1771
1772 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
1774 symbols.push(Symbol {
1775 name: node_text(source, &name_node).to_string(),
1776 kind: SymbolKind::Enum,
1777 range: node_range_with_decorators(&def_node, source, lang),
1778 signature: Some(extract_signature(source, &def_node)),
1779 scope_chain: vec![],
1780 exported: is_exported(&def_node, &export_ranges),
1781 parent: None,
1782 });
1783 }
1784
1785 if let (Some(name_node), Some(def_node)) = (type_alias_name_node, type_alias_def_node) {
1787 symbols.push(Symbol {
1788 name: node_text(source, &name_node).to_string(),
1789 kind: SymbolKind::TypeAlias,
1790 range: node_range_with_decorators(&def_node, source, lang),
1791 signature: Some(extract_signature(source, &def_node)),
1792 scope_chain: vec![],
1793 exported: is_exported(&def_node, &export_ranges),
1794 parent: None,
1795 });
1796 }
1797
1798 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
1800 let is_top_level = def_node
1802 .parent()
1803 .map(|p| p.kind() == "program" || p.kind() == "export_statement")
1804 .unwrap_or(false);
1805 let is_function_like = lexical_declaration_has_function_value(&def_node);
1806 let name = node_text(source, &name_node).to_string();
1807 let already_captured = symbols.iter().any(|s| s.name == name);
1808 if is_top_level && !is_function_like && !already_captured {
1809 symbols.push(Symbol {
1810 name,
1811 kind: SymbolKind::Variable,
1812 range: node_range_with_decorators(&def_node, source, lang),
1813 signature: Some(extract_signature(source, &def_node)),
1814 scope_chain: vec![],
1815 exported: is_exported(&def_node, &export_ranges),
1816 parent: None,
1817 });
1818 }
1819 }
1820 }
1821
1822 mark_named_exports(&mut symbols, &exported_names);
1823
1824 dedup_symbols(&mut symbols);
1826 Ok(symbols)
1827}
1828
1829fn extract_js_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1831 let lang = LangId::JavaScript;
1832 let capture_names = query.capture_names();
1833
1834 let export_ranges = collect_export_ranges(source, root, query);
1835 let exported_names = collect_exported_symbol_names(source, root);
1836
1837 let mut symbols = Vec::new();
1838 let mut cursor = QueryCursor::new();
1839 let mut matches = cursor.matches(query, *root, source.as_bytes());
1840
1841 while let Some(m) = {
1842 matches.advance();
1843 matches.get()
1844 } {
1845 let mut fn_name_node = None;
1846 let mut fn_def_node = None;
1847 let mut arrow_name_node = None;
1848 let mut arrow_def_node = None;
1849 let mut class_name_node = None;
1850 let mut class_def_node = None;
1851 let mut method_class_name_node = None;
1852 let mut method_name_node = None;
1853 let mut method_def_node = None;
1854 let mut default_body_node = None;
1855 let mut default_def_node = None;
1856
1857 for cap in m.captures {
1858 let Some(&name) = capture_names.get(cap.index as usize) else {
1859 continue;
1860 };
1861 match name {
1862 "fn.name" => fn_name_node = Some(cap.node),
1863 "fn.def" => fn_def_node = Some(cap.node),
1864 "arrow.name" => arrow_name_node = Some(cap.node),
1865 "arrow.def" => arrow_def_node = Some(cap.node),
1866 "class.name" => class_name_node = Some(cap.node),
1867 "class.def" => class_def_node = Some(cap.node),
1868 "method.class_name" => method_class_name_node = Some(cap.node),
1869 "method.name" => method_name_node = Some(cap.node),
1870 "method.def" => method_def_node = Some(cap.node),
1871 "default.body" => default_body_node = Some(cap.node),
1872 "default.def" => default_def_node = Some(cap.node),
1873 _ => {}
1874 }
1875 }
1876
1877 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
1878 symbols.push(Symbol {
1879 name: node_text(source, &name_node).to_string(),
1880 kind: SymbolKind::Function,
1881 range: node_range_with_decorators(&def_node, source, lang),
1882 signature: Some(extract_signature(source, &def_node)),
1883 scope_chain: vec![],
1884 exported: is_exported(&def_node, &export_ranges),
1885 parent: None,
1886 });
1887 }
1888
1889 if let (Some(name_node), Some(def_node)) = (arrow_name_node, arrow_def_node) {
1890 symbols.push(Symbol {
1891 name: node_text(source, &name_node).to_string(),
1892 kind: SymbolKind::Function,
1893 range: node_range_with_decorators(&def_node, source, lang),
1894 signature: Some(extract_signature(source, &def_node)),
1895 scope_chain: vec![],
1896 exported: is_exported(&def_node, &export_ranges),
1897 parent: None,
1898 });
1899 }
1900
1901 if let (Some(body_node), Some(def_node)) = (default_body_node, default_def_node) {
1902 push_default_export_symbol(&mut symbols, source, lang, body_node, def_node);
1903 }
1904
1905 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
1906 symbols.push(Symbol {
1907 name: node_text(source, &name_node).to_string(),
1908 kind: SymbolKind::Class,
1909 range: node_range_with_decorators(&def_node, source, lang),
1910 signature: Some(extract_signature(source, &def_node)),
1911 scope_chain: vec![],
1912 exported: is_exported(&def_node, &export_ranges),
1913 parent: None,
1914 });
1915 }
1916
1917 if let (Some(class_name_node), Some(name_node), Some(def_node)) =
1918 (method_class_name_node, method_name_node, method_def_node)
1919 {
1920 let class_name = node_text(source, &class_name_node).to_string();
1921 symbols.push(Symbol {
1922 name: node_text(source, &name_node).to_string(),
1923 kind: SymbolKind::Method,
1924 range: node_range_with_decorators(&def_node, source, lang),
1925 signature: Some(extract_signature(source, &def_node)),
1926 scope_chain: vec![class_name.clone()],
1927 exported: false,
1928 parent: Some(class_name),
1929 });
1930 }
1931 }
1932
1933 mark_named_exports(&mut symbols, &exported_names);
1934 dedup_symbols(&mut symbols);
1935 Ok(symbols)
1936}
1937
1938fn py_scope_chain(node: &Node, source: &str) -> Vec<String> {
1941 let mut chain = Vec::new();
1942 let mut current = node.parent();
1943 while let Some(parent) = current {
1944 if parent.kind() == "class_definition" {
1945 if let Some(name_node) = parent.child_by_field_name("name") {
1946 chain.push(node_text(source, &name_node).to_string());
1947 }
1948 }
1949 current = parent.parent();
1950 }
1951 chain.reverse();
1952 chain
1953}
1954
1955fn extract_py_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
1957 let lang = LangId::Python;
1958 let capture_names = query.capture_names();
1959
1960 let mut symbols = Vec::new();
1961 let mut cursor = QueryCursor::new();
1962 let mut matches = cursor.matches(query, *root, source.as_bytes());
1963
1964 let mut decorated_fn_lines = std::collections::HashSet::new();
1966
1967 {
1969 let mut cursor2 = QueryCursor::new();
1970 let mut matches2 = cursor2.matches(query, *root, source.as_bytes());
1971 while let Some(m) = {
1972 matches2.advance();
1973 matches2.get()
1974 } {
1975 let mut dec_def_node = None;
1976 let mut dec_decorator_node = None;
1977
1978 for cap in m.captures {
1979 let Some(&name) = capture_names.get(cap.index as usize) else {
1980 continue;
1981 };
1982 match name {
1983 "dec.def" => dec_def_node = Some(cap.node),
1984 "dec.decorator" => dec_decorator_node = Some(cap.node),
1985 _ => {}
1986 }
1987 }
1988
1989 if let (Some(def_node), Some(_dec_node)) = (dec_def_node, dec_decorator_node) {
1990 let mut child_cursor = def_node.walk();
1992 if child_cursor.goto_first_child() {
1993 loop {
1994 let child = child_cursor.node();
1995 if child.kind() == "function_definition"
1996 || child.kind() == "class_definition"
1997 {
1998 decorated_fn_lines.insert(child.start_position().row);
1999 }
2000 if !child_cursor.goto_next_sibling() {
2001 break;
2002 }
2003 }
2004 }
2005 }
2006 }
2007 }
2008
2009 while let Some(m) = {
2010 matches.advance();
2011 matches.get()
2012 } {
2013 let mut fn_name_node = None;
2014 let mut fn_def_node = None;
2015 let mut class_name_node = None;
2016 let mut class_def_node = None;
2017
2018 for cap in m.captures {
2019 let Some(&name) = capture_names.get(cap.index as usize) else {
2020 continue;
2021 };
2022 match name {
2023 "fn.name" => fn_name_node = Some(cap.node),
2024 "fn.def" => fn_def_node = Some(cap.node),
2025 "class.name" => class_name_node = Some(cap.node),
2026 "class.def" => class_def_node = Some(cap.node),
2027 _ => {}
2028 }
2029 }
2030
2031 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2033 let scope = py_scope_chain(&def_node, source);
2034 let is_method = !scope.is_empty();
2035 let name = node_text(source, &name_node).to_string();
2036 let kind = if is_method {
2038 SymbolKind::Method
2039 } else {
2040 SymbolKind::Function
2041 };
2042
2043 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
2045 let mut sig_parts = Vec::new();
2047 let mut parent = def_node.parent();
2048 while let Some(p) = parent {
2049 if p.kind() == "decorated_definition" {
2050 let mut dc = p.walk();
2052 if dc.goto_first_child() {
2053 loop {
2054 if dc.node().kind() == "decorator" {
2055 sig_parts.push(node_text(source, &dc.node()).to_string());
2056 }
2057 if !dc.goto_next_sibling() {
2058 break;
2059 }
2060 }
2061 }
2062 break;
2063 }
2064 parent = p.parent();
2065 }
2066 sig_parts.push(extract_signature(source, &def_node));
2067 Some(sig_parts.join("\n"))
2068 } else {
2069 Some(extract_signature(source, &def_node))
2070 };
2071
2072 symbols.push(Symbol {
2073 name,
2074 kind,
2075 range: node_range_with_decorators(&def_node, source, lang),
2076 signature: sig,
2077 scope_chain: scope.clone(),
2078 exported: false, parent: scope.last().cloned(),
2080 });
2081 }
2082
2083 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2085 let scope = py_scope_chain(&def_node, source);
2086
2087 let sig = if decorated_fn_lines.contains(&def_node.start_position().row) {
2089 let mut sig_parts = Vec::new();
2090 let mut parent = def_node.parent();
2091 while let Some(p) = parent {
2092 if p.kind() == "decorated_definition" {
2093 let mut dc = p.walk();
2094 if dc.goto_first_child() {
2095 loop {
2096 if dc.node().kind() == "decorator" {
2097 sig_parts.push(node_text(source, &dc.node()).to_string());
2098 }
2099 if !dc.goto_next_sibling() {
2100 break;
2101 }
2102 }
2103 }
2104 break;
2105 }
2106 parent = p.parent();
2107 }
2108 sig_parts.push(extract_signature(source, &def_node));
2109 Some(sig_parts.join("\n"))
2110 } else {
2111 Some(extract_signature(source, &def_node))
2112 };
2113
2114 symbols.push(Symbol {
2115 name: node_text(source, &name_node).to_string(),
2116 kind: SymbolKind::Class,
2117 range: node_range_with_decorators(&def_node, source, lang),
2118 signature: sig,
2119 scope_chain: scope.clone(),
2120 exported: false,
2121 parent: scope.last().cloned(),
2122 });
2123 }
2124 }
2125
2126 dedup_symbols(&mut symbols);
2127 Ok(symbols)
2128}
2129
2130fn extract_rs_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2133 let lang = LangId::Rust;
2134 let capture_names = query.capture_names();
2135
2136 let mut vis_ranges: Vec<std::ops::Range<usize>> = Vec::new();
2138 {
2139 let vis_idx = capture_names.iter().position(|n| *n == "vis.mod");
2140 if let Some(idx) = vis_idx {
2141 let idx = idx as u32;
2142 let mut cursor = QueryCursor::new();
2143 let mut matches = cursor.matches(query, *root, source.as_bytes());
2144 while let Some(m) = {
2145 matches.advance();
2146 matches.get()
2147 } {
2148 for cap in m.captures {
2149 if cap.index == idx {
2150 vis_ranges.push(cap.node.byte_range());
2151 }
2152 }
2153 }
2154 }
2155 }
2156
2157 let is_pub = |node: &Node| -> bool {
2158 let mut child_cursor = node.walk();
2160 if child_cursor.goto_first_child() {
2161 loop {
2162 if child_cursor.node().kind() == "visibility_modifier" {
2163 return true;
2164 }
2165 if !child_cursor.goto_next_sibling() {
2166 break;
2167 }
2168 }
2169 }
2170 false
2171 };
2172
2173 let mut symbols = Vec::new();
2174 let mut cursor = QueryCursor::new();
2175 let mut matches = cursor.matches(query, *root, source.as_bytes());
2176
2177 while let Some(m) = {
2178 matches.advance();
2179 matches.get()
2180 } {
2181 let mut fn_name_node = None;
2182 let mut fn_def_node = None;
2183 let mut struct_name_node = None;
2184 let mut struct_def_node = None;
2185 let mut enum_name_node = None;
2186 let mut enum_def_node = None;
2187 let mut trait_name_node = None;
2188 let mut trait_def_node = None;
2189 let mut impl_def_node = None;
2190
2191 for cap in m.captures {
2192 let Some(&name) = capture_names.get(cap.index as usize) else {
2193 continue;
2194 };
2195 match name {
2196 "fn.name" => fn_name_node = Some(cap.node),
2197 "fn.def" => fn_def_node = Some(cap.node),
2198 "struct.name" => struct_name_node = Some(cap.node),
2199 "struct.def" => struct_def_node = Some(cap.node),
2200 "enum.name" => enum_name_node = Some(cap.node),
2201 "enum.def" => enum_def_node = Some(cap.node),
2202 "trait.name" => trait_name_node = Some(cap.node),
2203 "trait.def" => trait_def_node = Some(cap.node),
2204 "impl.def" => impl_def_node = Some(cap.node),
2205 _ => {}
2206 }
2207 }
2208
2209 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2211 let parent = def_node.parent();
2212 let in_impl = parent
2213 .map(|p| p.kind() == "declaration_list")
2214 .unwrap_or(false);
2215 if !in_impl {
2216 symbols.push(Symbol {
2217 name: node_text(source, &name_node).to_string(),
2218 kind: SymbolKind::Function,
2219 range: node_range_with_decorators(&def_node, source, lang),
2220 signature: Some(extract_signature(source, &def_node)),
2221 scope_chain: vec![],
2222 exported: is_pub(&def_node),
2223 parent: None,
2224 });
2225 }
2226 }
2227
2228 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2230 symbols.push(Symbol {
2231 name: node_text(source, &name_node).to_string(),
2232 kind: SymbolKind::Struct,
2233 range: node_range_with_decorators(&def_node, source, lang),
2234 signature: Some(extract_signature(source, &def_node)),
2235 scope_chain: vec![],
2236 exported: is_pub(&def_node),
2237 parent: None,
2238 });
2239 }
2240
2241 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2243 symbols.push(Symbol {
2244 name: node_text(source, &name_node).to_string(),
2245 kind: SymbolKind::Enum,
2246 range: node_range_with_decorators(&def_node, source, lang),
2247 signature: Some(extract_signature(source, &def_node)),
2248 scope_chain: vec![],
2249 exported: is_pub(&def_node),
2250 parent: None,
2251 });
2252 }
2253
2254 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
2256 symbols.push(Symbol {
2257 name: node_text(source, &name_node).to_string(),
2258 kind: SymbolKind::Interface,
2259 range: node_range_with_decorators(&def_node, source, lang),
2260 signature: Some(extract_signature(source, &def_node)),
2261 scope_chain: vec![],
2262 exported: is_pub(&def_node),
2263 parent: None,
2264 });
2265 }
2266
2267 if let Some(impl_node) = impl_def_node {
2269 let mut type_names: Vec<String> = Vec::new();
2273 let mut child_cursor = impl_node.walk();
2274 if child_cursor.goto_first_child() {
2275 loop {
2276 let child = child_cursor.node();
2277 if child.kind() == "type_identifier" || child.kind() == "generic_type" {
2278 type_names.push(node_text(source, &child).to_string());
2279 }
2280 if !child_cursor.goto_next_sibling() {
2281 break;
2282 }
2283 }
2284 }
2285
2286 let scope_name = if type_names.len() >= 2 {
2287 format!("{} for {}", type_names[0], type_names[1])
2289 } else if type_names.len() == 1 {
2290 type_names[0].clone()
2291 } else {
2292 String::new()
2293 };
2294
2295 let parent_name = type_names.last().cloned().unwrap_or_default();
2296
2297 let mut child_cursor = impl_node.walk();
2299 if child_cursor.goto_first_child() {
2300 loop {
2301 let child = child_cursor.node();
2302 if child.kind() == "declaration_list" {
2303 let mut fn_cursor = child.walk();
2304 if fn_cursor.goto_first_child() {
2305 loop {
2306 let fn_node = fn_cursor.node();
2307 if fn_node.kind() == "function_item" {
2308 if let Some(name_node) = fn_node.child_by_field_name("name") {
2309 symbols.push(Symbol {
2310 name: node_text(source, &name_node).to_string(),
2311 kind: SymbolKind::Method,
2312 range: node_range_with_decorators(
2313 &fn_node, source, lang,
2314 ),
2315 signature: Some(extract_signature(source, &fn_node)),
2316 scope_chain: if scope_name.is_empty() {
2317 vec![]
2318 } else {
2319 vec![scope_name.clone()]
2320 },
2321 exported: is_pub(&fn_node),
2322 parent: if parent_name.is_empty() {
2323 None
2324 } else {
2325 Some(parent_name.clone())
2326 },
2327 });
2328 }
2329 }
2330 if !fn_cursor.goto_next_sibling() {
2331 break;
2332 }
2333 }
2334 }
2335 }
2336 if !child_cursor.goto_next_sibling() {
2337 break;
2338 }
2339 }
2340 }
2341 }
2342 }
2343
2344 dedup_symbols(&mut symbols);
2345 Ok(symbols)
2346}
2347
2348fn extract_go_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2352 let lang = LangId::Go;
2353 let capture_names = query.capture_names();
2354
2355 let is_go_exported = |name: &str| -> bool {
2356 name.chars()
2357 .next()
2358 .map(|c| c.is_uppercase())
2359 .unwrap_or(false)
2360 };
2361
2362 let mut symbols = Vec::new();
2363 let mut cursor = QueryCursor::new();
2364 let mut matches = cursor.matches(query, *root, source.as_bytes());
2365
2366 while let Some(m) = {
2367 matches.advance();
2368 matches.get()
2369 } {
2370 let mut fn_name_node = None;
2371 let mut fn_def_node = None;
2372 let mut method_name_node = None;
2373 let mut method_def_node = None;
2374 let mut type_name_node = None;
2375 let mut type_body_node = None;
2376 let mut type_def_node = None;
2377
2378 for cap in m.captures {
2379 let Some(&name) = capture_names.get(cap.index as usize) else {
2380 continue;
2381 };
2382 match name {
2383 "fn.name" => fn_name_node = Some(cap.node),
2384 "fn.def" => fn_def_node = Some(cap.node),
2385 "method.name" => method_name_node = Some(cap.node),
2386 "method.def" => method_def_node = Some(cap.node),
2387 "type.name" => type_name_node = Some(cap.node),
2388 "type.body" => type_body_node = Some(cap.node),
2389 "type.def" => type_def_node = Some(cap.node),
2390 _ => {}
2391 }
2392 }
2393
2394 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2396 let name = node_text(source, &name_node).to_string();
2397 symbols.push(Symbol {
2398 exported: is_go_exported(&name),
2399 name,
2400 kind: SymbolKind::Function,
2401 range: node_range_with_decorators(&def_node, source, lang),
2402 signature: Some(extract_signature(source, &def_node)),
2403 scope_chain: vec![],
2404 parent: None,
2405 });
2406 }
2407
2408 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2410 let name = node_text(source, &name_node).to_string();
2411
2412 let receiver_type = extract_go_receiver_type(&def_node, source);
2414 let scope_chain = if let Some(ref rt) = receiver_type {
2415 vec![rt.clone()]
2416 } else {
2417 vec![]
2418 };
2419
2420 symbols.push(Symbol {
2421 exported: is_go_exported(&name),
2422 name,
2423 kind: SymbolKind::Method,
2424 range: node_range_with_decorators(&def_node, source, lang),
2425 signature: Some(extract_signature(source, &def_node)),
2426 scope_chain,
2427 parent: receiver_type,
2428 });
2429 }
2430
2431 if let (Some(name_node), Some(body_node), Some(def_node)) =
2433 (type_name_node, type_body_node, type_def_node)
2434 {
2435 let name = node_text(source, &name_node).to_string();
2436 let kind = match body_node.kind() {
2437 "struct_type" => SymbolKind::Struct,
2438 "interface_type" => SymbolKind::Interface,
2439 _ => SymbolKind::TypeAlias,
2440 };
2441
2442 symbols.push(Symbol {
2443 exported: is_go_exported(&name),
2444 name,
2445 kind,
2446 range: node_range_with_decorators(&def_node, source, lang),
2447 signature: Some(extract_signature(source, &def_node)),
2448 scope_chain: vec![],
2449 parent: None,
2450 });
2451 }
2452 }
2453
2454 dedup_symbols(&mut symbols);
2455 Ok(symbols)
2456}
2457
2458fn extract_go_receiver_type(method_node: &Node, source: &str) -> Option<String> {
2461 let mut child_cursor = method_node.walk();
2463 if child_cursor.goto_first_child() {
2464 loop {
2465 let child = child_cursor.node();
2466 if child.kind() == "parameter_list" {
2467 return find_type_identifier_recursive(&child, source);
2469 }
2470 if !child_cursor.goto_next_sibling() {
2471 break;
2472 }
2473 }
2474 }
2475 None
2476}
2477
2478fn split_scope_text(text: &str, separator: &str) -> Vec<String> {
2479 text.split(separator)
2480 .map(str::trim)
2481 .filter(|segment| !segment.is_empty())
2482 .map(ToString::to_string)
2483 .collect()
2484}
2485
2486fn last_scope_segment(text: &str, separator: &str) -> String {
2487 split_scope_text(text, separator)
2488 .pop()
2489 .unwrap_or_else(|| text.trim().to_string())
2490}
2491
2492fn zig_container_scope_chain(node: &Node, source: &str) -> Vec<String> {
2493 let mut chain = Vec::new();
2494 let mut current = node.parent();
2495
2496 while let Some(parent) = current {
2497 if matches!(
2498 parent.kind(),
2499 "struct_declaration" | "enum_declaration" | "union_declaration" | "opaque_declaration"
2500 ) {
2501 if let Some(container) = parent.parent() {
2502 if container.kind() == "variable_declaration" {
2503 let mut cursor = container.walk();
2504 if cursor.goto_first_child() {
2505 loop {
2506 let child = cursor.node();
2507 if child.kind() == "identifier" {
2508 chain.push(node_text(source, &child).to_string());
2509 break;
2510 }
2511 if !cursor.goto_next_sibling() {
2512 break;
2513 }
2514 }
2515 }
2516 }
2517 }
2518 }
2519 current = parent.parent();
2520 }
2521
2522 chain.reverse();
2523 chain
2524}
2525
2526fn csharp_scope_chain(node: &Node, source: &str) -> Vec<String> {
2527 let mut chain = Vec::new();
2528 let mut current = node.parent();
2529
2530 while let Some(parent) = current {
2531 match parent.kind() {
2532 "namespace_declaration" | "file_scoped_namespace_declaration" => {
2533 if let Some(name_node) = parent.child_by_field_name("name") {
2534 chain.push(node_text(source, &name_node).to_string());
2535 }
2536 }
2537 "class_declaration"
2538 | "interface_declaration"
2539 | "struct_declaration"
2540 | "record_declaration" => {
2541 if let Some(name_node) = parent.child_by_field_name("name") {
2542 chain.push(node_text(source, &name_node).to_string());
2543 }
2544 }
2545 _ => {}
2546 }
2547 current = parent.parent();
2548 }
2549
2550 chain.reverse();
2551 chain
2552}
2553
2554fn cpp_parent_scope_chain(node: &Node, source: &str) -> Vec<String> {
2555 let mut chain = Vec::new();
2556 let mut current = node.parent();
2557
2558 while let Some(parent) = current {
2559 match parent.kind() {
2560 "namespace_definition" => {
2561 if let Some(name_node) = parent.child_by_field_name("name") {
2562 chain.push(node_text(source, &name_node).to_string());
2563 }
2564 }
2565 "class_specifier" | "struct_specifier" => {
2566 if let Some(name_node) = parent.child_by_field_name("name") {
2567 chain.push(last_scope_segment(node_text(source, &name_node), "::"));
2568 }
2569 }
2570 _ => {}
2571 }
2572 current = parent.parent();
2573 }
2574
2575 chain.reverse();
2576 chain
2577}
2578
2579fn template_signature(source: &str, template_node: &Node, item_node: &Node) -> String {
2580 format!(
2581 "{}\n{}",
2582 extract_signature(source, template_node),
2583 extract_signature(source, item_node)
2584 )
2585}
2586
2587fn extract_c_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2589 let lang = LangId::C;
2590 let capture_names = query.capture_names();
2591
2592 let mut symbols = Vec::new();
2593 let mut cursor = QueryCursor::new();
2594 let mut matches = cursor.matches(query, *root, source.as_bytes());
2595
2596 while let Some(m) = {
2597 matches.advance();
2598 matches.get()
2599 } {
2600 let mut fn_name_node = None;
2601 let mut fn_def_node = None;
2602 let mut struct_name_node = None;
2603 let mut struct_def_node = None;
2604 let mut enum_name_node = None;
2605 let mut enum_def_node = None;
2606 let mut type_name_node = None;
2607 let mut type_def_node = None;
2608 let mut macro_name_node = None;
2609 let mut macro_def_node = None;
2610
2611 for cap in m.captures {
2612 let Some(&name) = capture_names.get(cap.index as usize) else {
2613 continue;
2614 };
2615 match name {
2616 "fn.name" => fn_name_node = Some(cap.node),
2617 "fn.def" => fn_def_node = Some(cap.node),
2618 "struct.name" => struct_name_node = Some(cap.node),
2619 "struct.def" => struct_def_node = Some(cap.node),
2620 "enum.name" => enum_name_node = Some(cap.node),
2621 "enum.def" => enum_def_node = Some(cap.node),
2622 "type.name" => type_name_node = Some(cap.node),
2623 "type.def" => type_def_node = Some(cap.node),
2624 "macro.name" => macro_name_node = Some(cap.node),
2625 "macro.def" => macro_def_node = Some(cap.node),
2626 _ => {}
2627 }
2628 }
2629
2630 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2631 symbols.push(Symbol {
2632 name: node_text(source, &name_node).to_string(),
2633 kind: SymbolKind::Function,
2634 range: node_range_with_decorators(&def_node, source, lang),
2635 signature: Some(extract_signature(source, &def_node)),
2636 scope_chain: vec![],
2637 exported: false,
2638 parent: None,
2639 });
2640 }
2641
2642 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2643 symbols.push(Symbol {
2644 name: node_text(source, &name_node).to_string(),
2645 kind: SymbolKind::Struct,
2646 range: node_range_with_decorators(&def_node, source, lang),
2647 signature: Some(extract_signature(source, &def_node)),
2648 scope_chain: vec![],
2649 exported: false,
2650 parent: None,
2651 });
2652 }
2653
2654 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2655 symbols.push(Symbol {
2656 name: node_text(source, &name_node).to_string(),
2657 kind: SymbolKind::Enum,
2658 range: node_range_with_decorators(&def_node, source, lang),
2659 signature: Some(extract_signature(source, &def_node)),
2660 scope_chain: vec![],
2661 exported: false,
2662 parent: None,
2663 });
2664 }
2665
2666 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
2667 symbols.push(Symbol {
2668 name: node_text(source, &name_node).to_string(),
2669 kind: SymbolKind::TypeAlias,
2670 range: node_range_with_decorators(&def_node, source, lang),
2671 signature: Some(extract_signature(source, &def_node)),
2672 scope_chain: vec![],
2673 exported: false,
2674 parent: None,
2675 });
2676 }
2677
2678 if let (Some(name_node), Some(def_node)) = (macro_name_node, macro_def_node) {
2679 symbols.push(Symbol {
2680 name: node_text(source, &name_node).to_string(),
2681 kind: SymbolKind::Variable,
2682 range: node_range(&def_node),
2683 signature: Some(extract_signature(source, &def_node)),
2684 scope_chain: vec![],
2685 exported: false,
2686 parent: None,
2687 });
2688 }
2689 }
2690
2691 dedup_symbols(&mut symbols);
2692 Ok(symbols)
2693}
2694
2695fn extract_cpp_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
2697 let lang = LangId::Cpp;
2698 let capture_names = query.capture_names();
2699
2700 let mut type_names = HashSet::new();
2701 {
2702 let mut cursor = QueryCursor::new();
2703 let mut matches = cursor.matches(query, *root, source.as_bytes());
2704 while let Some(m) = {
2705 matches.advance();
2706 matches.get()
2707 } {
2708 for cap in m.captures {
2709 let Some(&name) = capture_names.get(cap.index as usize) else {
2710 continue;
2711 };
2712 match name {
2713 "class.name"
2714 | "struct.name"
2715 | "template.class.name"
2716 | "template.struct.name" => {
2717 type_names.insert(last_scope_segment(node_text(source, &cap.node), "::"));
2718 }
2719 _ => {}
2720 }
2721 }
2722 }
2723 }
2724
2725 let mut symbols = Vec::new();
2726 let mut cursor = QueryCursor::new();
2727 let mut matches = cursor.matches(query, *root, source.as_bytes());
2728
2729 while let Some(m) = {
2730 matches.advance();
2731 matches.get()
2732 } {
2733 let mut fn_name_node = None;
2734 let mut fn_def_node = None;
2735 let mut method_name_node = None;
2736 let mut method_def_node = None;
2737 let mut qual_scope_node = None;
2738 let mut qual_name_node = None;
2739 let mut qual_def_node = None;
2740 let mut class_name_node = None;
2741 let mut class_def_node = None;
2742 let mut struct_name_node = None;
2743 let mut struct_def_node = None;
2744 let mut enum_name_node = None;
2745 let mut enum_def_node = None;
2746 let mut namespace_name_node = None;
2747 let mut namespace_def_node = None;
2748 let mut template_class_name_node = None;
2749 let mut template_class_def_node = None;
2750 let mut template_class_item_node = None;
2751 let mut template_struct_name_node = None;
2752 let mut template_struct_def_node = None;
2753 let mut template_struct_item_node = None;
2754 let mut template_fn_name_node = None;
2755 let mut template_fn_def_node = None;
2756 let mut template_fn_item_node = None;
2757 let mut template_qual_scope_node = None;
2758 let mut template_qual_name_node = None;
2759 let mut template_qual_def_node = None;
2760 let mut template_qual_item_node = None;
2761
2762 for cap in m.captures {
2763 let Some(&name) = capture_names.get(cap.index as usize) else {
2764 continue;
2765 };
2766 match name {
2767 "fn.name" => fn_name_node = Some(cap.node),
2768 "fn.def" => fn_def_node = Some(cap.node),
2769 "method.name" => method_name_node = Some(cap.node),
2770 "method.def" => method_def_node = Some(cap.node),
2771 "qual.scope" => qual_scope_node = Some(cap.node),
2772 "qual.name" => qual_name_node = Some(cap.node),
2773 "qual.def" => qual_def_node = Some(cap.node),
2774 "class.name" => class_name_node = Some(cap.node),
2775 "class.def" => class_def_node = Some(cap.node),
2776 "struct.name" => struct_name_node = Some(cap.node),
2777 "struct.def" => struct_def_node = Some(cap.node),
2778 "enum.name" => enum_name_node = Some(cap.node),
2779 "enum.def" => enum_def_node = Some(cap.node),
2780 "namespace.name" => namespace_name_node = Some(cap.node),
2781 "namespace.def" => namespace_def_node = Some(cap.node),
2782 "template.class.name" => template_class_name_node = Some(cap.node),
2783 "template.class.def" => template_class_def_node = Some(cap.node),
2784 "template.class.item" => template_class_item_node = Some(cap.node),
2785 "template.struct.name" => template_struct_name_node = Some(cap.node),
2786 "template.struct.def" => template_struct_def_node = Some(cap.node),
2787 "template.struct.item" => template_struct_item_node = Some(cap.node),
2788 "template.fn.name" => template_fn_name_node = Some(cap.node),
2789 "template.fn.def" => template_fn_def_node = Some(cap.node),
2790 "template.fn.item" => template_fn_item_node = Some(cap.node),
2791 "template.qual.scope" => template_qual_scope_node = Some(cap.node),
2792 "template.qual.name" => template_qual_name_node = Some(cap.node),
2793 "template.qual.def" => template_qual_def_node = Some(cap.node),
2794 "template.qual.item" => template_qual_item_node = Some(cap.node),
2795 _ => {}
2796 }
2797 }
2798
2799 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
2800 let in_template = def_node
2801 .parent()
2802 .map(|parent| parent.kind() == "template_declaration")
2803 .unwrap_or(false);
2804 if !in_template {
2805 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2806 symbols.push(Symbol {
2807 name: node_text(source, &name_node).to_string(),
2808 kind: SymbolKind::Function,
2809 range: node_range_with_decorators(&def_node, source, lang),
2810 signature: Some(extract_signature(source, &def_node)),
2811 scope_chain: scope_chain.clone(),
2812 exported: false,
2813 parent: scope_chain.last().cloned(),
2814 });
2815 }
2816 }
2817
2818 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
2819 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2820 symbols.push(Symbol {
2821 name: node_text(source, &name_node).to_string(),
2822 kind: SymbolKind::Method,
2823 range: node_range_with_decorators(&def_node, source, lang),
2824 signature: Some(extract_signature(source, &def_node)),
2825 scope_chain: scope_chain.clone(),
2826 exported: false,
2827 parent: scope_chain.last().cloned(),
2828 });
2829 }
2830
2831 if let (Some(scope_node), Some(name_node), Some(def_node)) =
2832 (qual_scope_node, qual_name_node, qual_def_node)
2833 {
2834 let in_template = def_node
2835 .parent()
2836 .map(|parent| parent.kind() == "template_declaration")
2837 .unwrap_or(false);
2838 if !in_template {
2839 let scope_text = node_text(source, &scope_node);
2840 let scope_chain = split_scope_text(scope_text, "::");
2841 let parent = scope_chain.last().cloned();
2842 let kind = if parent
2843 .as_ref()
2844 .map(|segment| type_names.contains(segment))
2845 .unwrap_or(false)
2846 {
2847 SymbolKind::Method
2848 } else {
2849 SymbolKind::Function
2850 };
2851
2852 symbols.push(Symbol {
2853 name: node_text(source, &name_node).to_string(),
2854 kind,
2855 range: node_range_with_decorators(&def_node, source, lang),
2856 signature: Some(extract_signature(source, &def_node)),
2857 scope_chain,
2858 exported: false,
2859 parent,
2860 });
2861 }
2862 }
2863
2864 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
2865 let in_template = def_node
2866 .parent()
2867 .map(|parent| parent.kind() == "template_declaration")
2868 .unwrap_or(false);
2869 if !in_template {
2870 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2871 let name = last_scope_segment(node_text(source, &name_node), "::");
2872 symbols.push(Symbol {
2873 name: name.clone(),
2874 kind: SymbolKind::Class,
2875 range: node_range_with_decorators(&def_node, source, lang),
2876 signature: Some(extract_signature(source, &def_node)),
2877 scope_chain: scope_chain.clone(),
2878 exported: false,
2879 parent: scope_chain.last().cloned(),
2880 });
2881 }
2882 }
2883
2884 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
2885 let in_template = def_node
2886 .parent()
2887 .map(|parent| parent.kind() == "template_declaration")
2888 .unwrap_or(false);
2889 if !in_template {
2890 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2891 let name = last_scope_segment(node_text(source, &name_node), "::");
2892 symbols.push(Symbol {
2893 name: name.clone(),
2894 kind: SymbolKind::Struct,
2895 range: node_range_with_decorators(&def_node, source, lang),
2896 signature: Some(extract_signature(source, &def_node)),
2897 scope_chain: scope_chain.clone(),
2898 exported: false,
2899 parent: scope_chain.last().cloned(),
2900 });
2901 }
2902 }
2903
2904 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
2905 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2906 let name = last_scope_segment(node_text(source, &name_node), "::");
2907 symbols.push(Symbol {
2908 name: name.clone(),
2909 kind: SymbolKind::Enum,
2910 range: node_range_with_decorators(&def_node, source, lang),
2911 signature: Some(extract_signature(source, &def_node)),
2912 scope_chain: scope_chain.clone(),
2913 exported: false,
2914 parent: scope_chain.last().cloned(),
2915 });
2916 }
2917
2918 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
2919 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2920 symbols.push(Symbol {
2921 name: node_text(source, &name_node).to_string(),
2922 kind: SymbolKind::TypeAlias,
2923 range: node_range_with_decorators(&def_node, source, lang),
2924 signature: Some(extract_signature(source, &def_node)),
2925 scope_chain: scope_chain.clone(),
2926 exported: false,
2927 parent: scope_chain.last().cloned(),
2928 });
2929 }
2930
2931 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2932 template_class_name_node,
2933 template_class_def_node,
2934 template_class_item_node,
2935 ) {
2936 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2937 let name = last_scope_segment(node_text(source, &name_node), "::");
2938 symbols.push(Symbol {
2939 name: name.clone(),
2940 kind: SymbolKind::Class,
2941 range: node_range_with_decorators(&def_node, source, lang),
2942 signature: Some(template_signature(source, &def_node, &item_node)),
2943 scope_chain: scope_chain.clone(),
2944 exported: false,
2945 parent: scope_chain.last().cloned(),
2946 });
2947 }
2948
2949 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2950 template_struct_name_node,
2951 template_struct_def_node,
2952 template_struct_item_node,
2953 ) {
2954 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2955 let name = last_scope_segment(node_text(source, &name_node), "::");
2956 symbols.push(Symbol {
2957 name: name.clone(),
2958 kind: SymbolKind::Struct,
2959 range: node_range_with_decorators(&def_node, source, lang),
2960 signature: Some(template_signature(source, &def_node, &item_node)),
2961 scope_chain: scope_chain.clone(),
2962 exported: false,
2963 parent: scope_chain.last().cloned(),
2964 });
2965 }
2966
2967 if let (Some(name_node), Some(def_node), Some(item_node)) = (
2968 template_fn_name_node,
2969 template_fn_def_node,
2970 template_fn_item_node,
2971 ) {
2972 let scope_chain = cpp_parent_scope_chain(&def_node, source);
2973 symbols.push(Symbol {
2974 name: node_text(source, &name_node).to_string(),
2975 kind: SymbolKind::Function,
2976 range: node_range_with_decorators(&def_node, source, lang),
2977 signature: Some(template_signature(source, &def_node, &item_node)),
2978 scope_chain: scope_chain.clone(),
2979 exported: false,
2980 parent: scope_chain.last().cloned(),
2981 });
2982 }
2983
2984 if let (Some(scope_node), Some(name_node), Some(def_node), Some(item_node)) = (
2985 template_qual_scope_node,
2986 template_qual_name_node,
2987 template_qual_def_node,
2988 template_qual_item_node,
2989 ) {
2990 let scope_chain = split_scope_text(node_text(source, &scope_node), "::");
2991 let parent = scope_chain.last().cloned();
2992 let kind = if parent
2993 .as_ref()
2994 .map(|segment| type_names.contains(segment))
2995 .unwrap_or(false)
2996 {
2997 SymbolKind::Method
2998 } else {
2999 SymbolKind::Function
3000 };
3001
3002 symbols.push(Symbol {
3003 name: node_text(source, &name_node).to_string(),
3004 kind,
3005 range: node_range_with_decorators(&def_node, source, lang),
3006 signature: Some(template_signature(source, &def_node, &item_node)),
3007 scope_chain,
3008 exported: false,
3009 parent,
3010 });
3011 }
3012 }
3013
3014 dedup_symbols(&mut symbols);
3015 Ok(symbols)
3016}
3017
3018fn extract_zig_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3020 let lang = LangId::Zig;
3021 let capture_names = query.capture_names();
3022
3023 let mut symbols = Vec::new();
3024 let mut cursor = QueryCursor::new();
3025 let mut matches = cursor.matches(query, *root, source.as_bytes());
3026
3027 while let Some(m) = {
3028 matches.advance();
3029 matches.get()
3030 } {
3031 let mut fn_name_node = None;
3032 let mut fn_def_node = None;
3033 let mut struct_name_node = None;
3034 let mut struct_def_node = None;
3035 let mut enum_name_node = None;
3036 let mut enum_def_node = None;
3037 let mut union_name_node = None;
3038 let mut union_def_node = None;
3039 let mut const_name_node = None;
3040 let mut const_def_node = None;
3041 let mut test_name_node = None;
3042 let mut test_def_node = None;
3043
3044 for cap in m.captures {
3045 let Some(&name) = capture_names.get(cap.index as usize) else {
3046 continue;
3047 };
3048 match name {
3049 "fn.name" => fn_name_node = Some(cap.node),
3050 "fn.def" => fn_def_node = Some(cap.node),
3051 "struct.name" => struct_name_node = Some(cap.node),
3052 "struct.def" => struct_def_node = Some(cap.node),
3053 "enum.name" => enum_name_node = Some(cap.node),
3054 "enum.def" => enum_def_node = Some(cap.node),
3055 "union.name" => union_name_node = Some(cap.node),
3056 "union.def" => union_def_node = Some(cap.node),
3057 "const.name" => const_name_node = Some(cap.node),
3058 "const.def" => const_def_node = Some(cap.node),
3059 "test.name" => test_name_node = Some(cap.node),
3060 "test.def" => test_def_node = Some(cap.node),
3061 _ => {}
3062 }
3063 }
3064
3065 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3066 let scope_chain = zig_container_scope_chain(&def_node, source);
3067 let kind = if scope_chain.is_empty() {
3068 SymbolKind::Function
3069 } else {
3070 SymbolKind::Method
3071 };
3072 symbols.push(Symbol {
3073 name: node_text(source, &name_node).to_string(),
3074 kind,
3075 range: node_range_with_decorators(&def_node, source, lang),
3076 signature: Some(extract_signature(source, &def_node)),
3077 scope_chain: scope_chain.clone(),
3078 exported: false,
3079 parent: scope_chain.last().cloned(),
3080 });
3081 }
3082
3083 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3084 symbols.push(Symbol {
3085 name: node_text(source, &name_node).to_string(),
3086 kind: SymbolKind::Struct,
3087 range: node_range_with_decorators(&def_node, source, lang),
3088 signature: Some(extract_signature(source, &def_node)),
3089 scope_chain: vec![],
3090 exported: false,
3091 parent: None,
3092 });
3093 }
3094
3095 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3096 symbols.push(Symbol {
3097 name: node_text(source, &name_node).to_string(),
3098 kind: SymbolKind::Enum,
3099 range: node_range_with_decorators(&def_node, source, lang),
3100 signature: Some(extract_signature(source, &def_node)),
3101 scope_chain: vec![],
3102 exported: false,
3103 parent: None,
3104 });
3105 }
3106
3107 if let (Some(name_node), Some(def_node)) = (union_name_node, union_def_node) {
3108 symbols.push(Symbol {
3109 name: node_text(source, &name_node).to_string(),
3110 kind: SymbolKind::TypeAlias,
3111 range: node_range_with_decorators(&def_node, source, lang),
3112 signature: Some(extract_signature(source, &def_node)),
3113 scope_chain: vec![],
3114 exported: false,
3115 parent: None,
3116 });
3117 }
3118
3119 if let (Some(name_node), Some(def_node)) = (const_name_node, const_def_node) {
3120 let signature = extract_signature(source, &def_node);
3121 let is_container = signature.contains("= struct")
3122 || signature.contains("= enum")
3123 || signature.contains("= union")
3124 || signature.contains("= opaque");
3125 let is_const = signature.trim_start().starts_with("const ");
3126 let name = node_text(source, &name_node).to_string();
3127 let already_captured = symbols.iter().any(|symbol| symbol.name == name);
3128 if is_const && !is_container && !already_captured {
3129 symbols.push(Symbol {
3130 name,
3131 kind: SymbolKind::Variable,
3132 range: node_range_with_decorators(&def_node, source, lang),
3133 signature: Some(signature),
3134 scope_chain: vec![],
3135 exported: false,
3136 parent: None,
3137 });
3138 }
3139 }
3140
3141 if let (Some(name_node), Some(def_node)) = (test_name_node, test_def_node) {
3142 let scope_chain = zig_container_scope_chain(&def_node, source);
3143 symbols.push(Symbol {
3144 name: node_text(source, &name_node).trim_matches('"').to_string(),
3145 kind: SymbolKind::Function,
3146 range: node_range_with_decorators(&def_node, source, lang),
3147 signature: Some(extract_signature(source, &def_node)),
3148 scope_chain: scope_chain.clone(),
3149 exported: false,
3150 parent: scope_chain.last().cloned(),
3151 });
3152 }
3153 }
3154
3155 dedup_symbols(&mut symbols);
3156 Ok(symbols)
3157}
3158
3159fn extract_csharp_symbols(
3161 source: &str,
3162 root: &Node,
3163 query: &Query,
3164) -> Result<Vec<Symbol>, AftError> {
3165 let lang = LangId::CSharp;
3166 let capture_names = query.capture_names();
3167
3168 let mut symbols = Vec::new();
3169 let mut cursor = QueryCursor::new();
3170 let mut matches = cursor.matches(query, *root, source.as_bytes());
3171
3172 while let Some(m) = {
3173 matches.advance();
3174 matches.get()
3175 } {
3176 let mut class_name_node = None;
3177 let mut class_def_node = None;
3178 let mut interface_name_node = None;
3179 let mut interface_def_node = None;
3180 let mut struct_name_node = None;
3181 let mut struct_def_node = None;
3182 let mut enum_name_node = None;
3183 let mut enum_def_node = None;
3184 let mut method_name_node = None;
3185 let mut method_def_node = None;
3186 let mut property_name_node = None;
3187 let mut property_def_node = None;
3188 let mut namespace_name_node = None;
3189 let mut namespace_def_node = None;
3190
3191 for cap in m.captures {
3192 let Some(&name) = capture_names.get(cap.index as usize) else {
3193 continue;
3194 };
3195 match name {
3196 "class.name" => class_name_node = Some(cap.node),
3197 "class.def" => class_def_node = Some(cap.node),
3198 "interface.name" => interface_name_node = Some(cap.node),
3199 "interface.def" => interface_def_node = Some(cap.node),
3200 "struct.name" => struct_name_node = Some(cap.node),
3201 "struct.def" => struct_def_node = Some(cap.node),
3202 "enum.name" => enum_name_node = Some(cap.node),
3203 "enum.def" => enum_def_node = Some(cap.node),
3204 "method.name" => method_name_node = Some(cap.node),
3205 "method.def" => method_def_node = Some(cap.node),
3206 "property.name" => property_name_node = Some(cap.node),
3207 "property.def" => property_def_node = Some(cap.node),
3208 "namespace.name" => namespace_name_node = Some(cap.node),
3209 "namespace.def" => namespace_def_node = Some(cap.node),
3210 _ => {}
3211 }
3212 }
3213
3214 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3215 let scope_chain = csharp_scope_chain(&def_node, source);
3216 symbols.push(Symbol {
3217 name: node_text(source, &name_node).to_string(),
3218 kind: SymbolKind::Class,
3219 range: node_range_with_decorators(&def_node, source, lang),
3220 signature: Some(extract_signature(source, &def_node)),
3221 scope_chain: scope_chain.clone(),
3222 exported: false,
3223 parent: scope_chain.last().cloned(),
3224 });
3225 }
3226
3227 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3228 let scope_chain = csharp_scope_chain(&def_node, source);
3229 symbols.push(Symbol {
3230 name: node_text(source, &name_node).to_string(),
3231 kind: SymbolKind::Interface,
3232 range: node_range_with_decorators(&def_node, source, lang),
3233 signature: Some(extract_signature(source, &def_node)),
3234 scope_chain: scope_chain.clone(),
3235 exported: false,
3236 parent: scope_chain.last().cloned(),
3237 });
3238 }
3239
3240 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3241 let scope_chain = csharp_scope_chain(&def_node, source);
3242 symbols.push(Symbol {
3243 name: node_text(source, &name_node).to_string(),
3244 kind: SymbolKind::Struct,
3245 range: node_range_with_decorators(&def_node, source, lang),
3246 signature: Some(extract_signature(source, &def_node)),
3247 scope_chain: scope_chain.clone(),
3248 exported: false,
3249 parent: scope_chain.last().cloned(),
3250 });
3251 }
3252
3253 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3254 let scope_chain = csharp_scope_chain(&def_node, source);
3255 symbols.push(Symbol {
3256 name: node_text(source, &name_node).to_string(),
3257 kind: SymbolKind::Enum,
3258 range: node_range_with_decorators(&def_node, source, lang),
3259 signature: Some(extract_signature(source, &def_node)),
3260 scope_chain: scope_chain.clone(),
3261 exported: false,
3262 parent: scope_chain.last().cloned(),
3263 });
3264 }
3265
3266 if let (Some(name_node), Some(def_node)) = (method_name_node, method_def_node) {
3267 let scope_chain = csharp_scope_chain(&def_node, source);
3268 symbols.push(Symbol {
3269 name: node_text(source, &name_node).to_string(),
3270 kind: SymbolKind::Method,
3271 range: node_range_with_decorators(&def_node, source, lang),
3272 signature: Some(extract_signature(source, &def_node)),
3273 scope_chain: scope_chain.clone(),
3274 exported: false,
3275 parent: scope_chain.last().cloned(),
3276 });
3277 }
3278
3279 if let (Some(name_node), Some(def_node)) = (property_name_node, property_def_node) {
3280 let scope_chain = csharp_scope_chain(&def_node, source);
3281 symbols.push(Symbol {
3282 name: node_text(source, &name_node).to_string(),
3283 kind: SymbolKind::Variable,
3284 range: node_range_with_decorators(&def_node, source, lang),
3285 signature: Some(extract_signature(source, &def_node)),
3286 scope_chain: scope_chain.clone(),
3287 exported: false,
3288 parent: scope_chain.last().cloned(),
3289 });
3290 }
3291
3292 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
3293 let scope_chain = csharp_scope_chain(&def_node, source);
3294 symbols.push(Symbol {
3295 name: node_text(source, &name_node).to_string(),
3296 kind: SymbolKind::TypeAlias,
3297 range: node_range_with_decorators(&def_node, source, lang),
3298 signature: Some(extract_signature(source, &def_node)),
3299 scope_chain: scope_chain.clone(),
3300 exported: false,
3301 parent: scope_chain.last().cloned(),
3302 });
3303 }
3304 }
3305
3306 dedup_symbols(&mut symbols);
3307 Ok(symbols)
3308}
3309
3310fn find_type_identifier_recursive(node: &Node, source: &str) -> Option<String> {
3312 if node.kind() == "type_identifier" {
3313 return Some(node_text(source, node).to_string());
3314 }
3315 let mut cursor = node.walk();
3316 if cursor.goto_first_child() {
3317 loop {
3318 if let Some(result) = find_type_identifier_recursive(&cursor.node(), source) {
3319 return Some(result);
3320 }
3321 if !cursor.goto_next_sibling() {
3322 break;
3323 }
3324 }
3325 }
3326 None
3327}
3328
3329fn extract_bash_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3333 let lang = LangId::Bash;
3334 let capture_names = query.capture_names();
3335
3336 let mut symbols = Vec::new();
3337 let mut cursor = QueryCursor::new();
3338 let mut matches = cursor.matches(query, *root, source.as_bytes());
3339
3340 while let Some(m) = {
3341 matches.advance();
3342 matches.get()
3343 } {
3344 let mut fn_name_node = None;
3345 let mut fn_def_node = None;
3346
3347 for cap in m.captures {
3348 let Some(&name) = capture_names.get(cap.index as usize) else {
3349 continue;
3350 };
3351 match name {
3352 "fn.name" => fn_name_node = Some(cap.node),
3353 "fn.def" => fn_def_node = Some(cap.node),
3354 _ => {}
3355 }
3356 }
3357
3358 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3359 symbols.push(Symbol {
3360 name: node_text(source, &name_node).to_string(),
3361 kind: SymbolKind::Function,
3362 range: node_range_with_decorators(&def_node, source, lang),
3363 signature: Some(extract_signature(source, &def_node)),
3364 scope_chain: vec![],
3365 exported: false,
3366 parent: None,
3367 });
3368 }
3369 }
3370
3371 Ok(symbols)
3372}
3373
3374fn solidity_scope_chain(node: &Node, source: &str) -> Vec<String> {
3377 let mut chain = Vec::new();
3378 let mut current = node.parent();
3379
3380 while let Some(parent) = current {
3381 match parent.kind() {
3382 "contract_declaration" | "library_declaration" | "interface_declaration" => {
3383 if let Some(name_node) = parent.child_by_field_name("name") {
3384 chain.push(node_text(source, &name_node).to_string());
3385 }
3386 }
3387 _ => {}
3388 }
3389 current = parent.parent();
3390 }
3391
3392 chain.reverse();
3393 chain
3394}
3395
3396fn extract_solidity_symbols(
3397 source: &str,
3398 root: &Node,
3399 query: &Query,
3400) -> Result<Vec<Symbol>, AftError> {
3401 let lang = LangId::Solidity;
3402 let capture_names = query.capture_names();
3403
3404 let mut symbols = Vec::new();
3405 let mut cursor = QueryCursor::new();
3406 let mut matches = cursor.matches(query, *root, source.as_bytes());
3407
3408 while let Some(m) = {
3409 matches.advance();
3410 matches.get()
3411 } {
3412 let mut contract_name_node = None;
3413 let mut contract_def_node = None;
3414 let mut library_name_node = None;
3415 let mut library_def_node = None;
3416 let mut interface_name_node = None;
3417 let mut interface_def_node = None;
3418 let mut fn_name_node = None;
3419 let mut fn_def_node = None;
3420 let mut modifier_name_node = None;
3421 let mut modifier_def_node = None;
3422 let mut constructor_def_node = None;
3423 let mut fallback_receive_def_node = None;
3424 let mut event_name_node = None;
3425 let mut event_def_node = None;
3426 let mut error_name_node = None;
3427 let mut error_def_node = None;
3428 let mut struct_name_node = None;
3429 let mut struct_def_node = None;
3430 let mut enum_name_node = None;
3431 let mut enum_def_node = None;
3432 let mut var_name_node = None;
3433 let mut var_def_node = None;
3434
3435 for cap in m.captures {
3436 let Some(&name) = capture_names.get(cap.index as usize) else {
3437 continue;
3438 };
3439 match name {
3440 "contract.name" => contract_name_node = Some(cap.node),
3441 "contract.def" => contract_def_node = Some(cap.node),
3442 "library.name" => library_name_node = Some(cap.node),
3443 "library.def" => library_def_node = Some(cap.node),
3444 "interface.name" => interface_name_node = Some(cap.node),
3445 "interface.def" => interface_def_node = Some(cap.node),
3446 "fn.name" => fn_name_node = Some(cap.node),
3447 "fn.def" => fn_def_node = Some(cap.node),
3448 "modifier.name" => modifier_name_node = Some(cap.node),
3449 "modifier.def" => modifier_def_node = Some(cap.node),
3450 "constructor.def" => constructor_def_node = Some(cap.node),
3451 "fallback_receive.def" => fallback_receive_def_node = Some(cap.node),
3452 "event.name" => event_name_node = Some(cap.node),
3453 "event.def" => event_def_node = Some(cap.node),
3454 "error.name" => error_name_node = Some(cap.node),
3455 "error.def" => error_def_node = Some(cap.node),
3456 "struct.name" => struct_name_node = Some(cap.node),
3457 "struct.def" => struct_def_node = Some(cap.node),
3458 "enum.name" => enum_name_node = Some(cap.node),
3459 "enum.def" => enum_def_node = Some(cap.node),
3460 "var.name" => var_name_node = Some(cap.node),
3461 "var.def" => var_def_node = Some(cap.node),
3462 _ => {}
3463 }
3464 }
3465
3466 if let (Some(name_node), Some(def_node)) = (contract_name_node, contract_def_node) {
3468 symbols.push(Symbol {
3469 name: node_text(source, &name_node).to_string(),
3470 kind: SymbolKind::Class,
3471 range: node_range_with_decorators(&def_node, source, lang),
3472 signature: Some(extract_signature(source, &def_node)),
3473 scope_chain: vec![],
3474 exported: true,
3475 parent: None,
3476 });
3477 }
3478
3479 if let (Some(name_node), Some(def_node)) = (library_name_node, library_def_node) {
3481 symbols.push(Symbol {
3482 name: node_text(source, &name_node).to_string(),
3483 kind: SymbolKind::Class,
3484 range: node_range_with_decorators(&def_node, source, lang),
3485 signature: Some(extract_signature(source, &def_node)),
3486 scope_chain: vec![],
3487 exported: true,
3488 parent: None,
3489 });
3490 }
3491
3492 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
3494 symbols.push(Symbol {
3495 name: node_text(source, &name_node).to_string(),
3496 kind: SymbolKind::Interface,
3497 range: node_range_with_decorators(&def_node, source, lang),
3498 signature: Some(extract_signature(source, &def_node)),
3499 scope_chain: vec![],
3500 exported: true,
3501 parent: None,
3502 });
3503 }
3504
3505 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3507 let scope_chain = solidity_scope_chain(&def_node, source);
3508 let kind = if scope_chain.is_empty() {
3509 SymbolKind::Function
3510 } else {
3511 SymbolKind::Method
3512 };
3513 symbols.push(Symbol {
3514 name: node_text(source, &name_node).to_string(),
3515 kind,
3516 range: node_range_with_decorators(&def_node, source, lang),
3517 signature: Some(extract_signature(source, &def_node)),
3518 parent: scope_chain.last().cloned(),
3519 scope_chain,
3520 exported: true,
3521 });
3522 }
3523
3524 if let (Some(name_node), Some(def_node)) = (modifier_name_node, modifier_def_node) {
3526 let scope_chain = solidity_scope_chain(&def_node, source);
3527 symbols.push(Symbol {
3528 name: node_text(source, &name_node).to_string(),
3529 kind: SymbolKind::Method,
3530 range: node_range_with_decorators(&def_node, source, lang),
3531 signature: Some(extract_signature(source, &def_node)),
3532 parent: scope_chain.last().cloned(),
3533 scope_chain,
3534 exported: true,
3535 });
3536 }
3537
3538 if let Some(def_node) = constructor_def_node {
3540 let scope_chain = solidity_scope_chain(&def_node, source);
3541 symbols.push(Symbol {
3542 name: "constructor".to_string(),
3543 kind: SymbolKind::Method,
3544 range: node_range_with_decorators(&def_node, source, lang),
3545 signature: Some(extract_signature(source, &def_node)),
3546 parent: scope_chain.last().cloned(),
3547 scope_chain,
3548 exported: true,
3549 });
3550 }
3551
3552 if let Some(def_node) = fallback_receive_def_node {
3554 let scope_chain = solidity_scope_chain(&def_node, source);
3555 let signature = extract_signature(source, &def_node);
3556 let name = if signature.trim_start().starts_with("receive") {
3557 "receive"
3558 } else {
3559 "fallback"
3560 };
3561 symbols.push(Symbol {
3562 name: name.to_string(),
3563 kind: SymbolKind::Method,
3564 range: node_range_with_decorators(&def_node, source, lang),
3565 signature: Some(signature),
3566 parent: scope_chain.last().cloned(),
3567 scope_chain,
3568 exported: true,
3569 });
3570 }
3571
3572 if let (Some(name_node), Some(def_node)) = (event_name_node, event_def_node) {
3574 let scope_chain = solidity_scope_chain(&def_node, source);
3575 symbols.push(Symbol {
3576 name: node_text(source, &name_node).to_string(),
3577 kind: SymbolKind::Function,
3578 range: node_range_with_decorators(&def_node, source, lang),
3579 signature: Some(extract_signature(source, &def_node)),
3580 parent: scope_chain.last().cloned(),
3581 scope_chain,
3582 exported: true,
3583 });
3584 }
3585
3586 if let (Some(name_node), Some(def_node)) = (error_name_node, error_def_node) {
3588 let scope_chain = solidity_scope_chain(&def_node, source);
3589 symbols.push(Symbol {
3590 name: node_text(source, &name_node).to_string(),
3591 kind: SymbolKind::TypeAlias,
3592 range: node_range_with_decorators(&def_node, source, lang),
3593 signature: Some(extract_signature(source, &def_node)),
3594 parent: scope_chain.last().cloned(),
3595 scope_chain,
3596 exported: true,
3597 });
3598 }
3599
3600 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
3602 let scope_chain = solidity_scope_chain(&def_node, source);
3603 symbols.push(Symbol {
3604 name: node_text(source, &name_node).to_string(),
3605 kind: SymbolKind::Struct,
3606 range: node_range_with_decorators(&def_node, source, lang),
3607 signature: Some(extract_signature(source, &def_node)),
3608 parent: scope_chain.last().cloned(),
3609 scope_chain,
3610 exported: true,
3611 });
3612 }
3613
3614 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3616 let scope_chain = solidity_scope_chain(&def_node, source);
3617 symbols.push(Symbol {
3618 name: node_text(source, &name_node).to_string(),
3619 kind: SymbolKind::Enum,
3620 range: node_range_with_decorators(&def_node, source, lang),
3621 signature: Some(extract_signature(source, &def_node)),
3622 parent: scope_chain.last().cloned(),
3623 scope_chain,
3624 exported: true,
3625 });
3626 }
3627
3628 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
3630 let scope_chain = solidity_scope_chain(&def_node, source);
3631 symbols.push(Symbol {
3632 name: node_text(source, &name_node).to_string(),
3633 kind: SymbolKind::Variable,
3634 range: node_range_with_decorators(&def_node, source, lang),
3635 signature: Some(extract_signature(source, &def_node)),
3636 parent: scope_chain.last().cloned(),
3637 scope_chain,
3638 exported: true,
3639 });
3640 }
3641 }
3642
3643 dedup_symbols(&mut symbols);
3644 Ok(symbols)
3645}
3646
3647fn extract_json_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
3648 let Some(value) = root.named_child(0) else {
3649 return Ok(Vec::new());
3650 };
3651
3652 if value.kind() != "object" {
3653 return Ok(Vec::new());
3654 }
3655
3656 let mut symbols = Vec::new();
3657 let mut cursor = value.walk();
3658 for child in value.named_children(&mut cursor) {
3659 if child.kind() != "pair" {
3660 continue;
3661 }
3662 let Some(key_node) = child.child_by_field_name("key") else {
3663 continue;
3664 };
3665 let name = node_text(source, &key_node).trim_matches('"').to_string();
3666 if name.is_empty() {
3667 continue;
3668 }
3669 symbols.push(Symbol {
3670 name,
3671 kind: SymbolKind::Variable,
3672 range: node_range_with_decorators(&child, source, LangId::Json),
3673 signature: None,
3674 scope_chain: vec![],
3675 exported: false,
3676 parent: None,
3677 });
3678 }
3679
3680 Ok(symbols)
3681}
3682
3683fn scala_scope_chain(node: &Node, source: &str) -> Vec<String> {
3684 let mut chain = Vec::new();
3685 let mut current = node.parent();
3686
3687 while let Some(parent) = current {
3688 match parent.kind() {
3689 "class_definition" | "object_definition" | "enum_definition" | "trait_definition" => {
3690 if let Some(name_node) = parent.child_by_field_name("name") {
3691 chain.push(node_text(source, &name_node).to_string());
3692 }
3693 }
3694 _ => {}
3695 }
3696 current = parent.parent();
3697 }
3698
3699 chain.reverse();
3700 chain
3701}
3702
3703fn extract_scala_symbols(
3704 source: &str,
3705 root: &Node,
3706 query: &Query,
3707) -> Result<Vec<Symbol>, AftError> {
3708 let lang = LangId::Scala;
3709 let capture_names = query.capture_names();
3710
3711 let mut symbols = Vec::new();
3712 let mut cursor = QueryCursor::new();
3713 let mut matches = cursor.matches(query, *root, source.as_bytes());
3714
3715 while let Some(m) = {
3716 matches.advance();
3717 matches.get()
3718 } {
3719 let mut class_name_node = None;
3720 let mut class_def_node = None;
3721 let mut object_name_node = None;
3722 let mut object_def_node = None;
3723 let mut enum_name_node = None;
3724 let mut enum_def_node = None;
3725 let mut trait_name_node = None;
3726 let mut trait_def_node = None;
3727 let mut fn_name_node = None;
3728 let mut fn_def_node = None;
3729 let mut val_name_node = None;
3730 let mut val_def_node = None;
3731 let mut var_name_node = None;
3732 let mut var_def_node = None;
3733 let mut type_name_node = None;
3734 let mut type_def_node = None;
3735
3736 for cap in m.captures {
3737 let Some(&name) = capture_names.get(cap.index as usize) else {
3738 continue;
3739 };
3740 match name {
3741 "class.name" => class_name_node = Some(cap.node),
3742 "class.def" => class_def_node = Some(cap.node),
3743 "object.name" => object_name_node = Some(cap.node),
3744 "object.def" => object_def_node = Some(cap.node),
3745 "enum.name" => enum_name_node = Some(cap.node),
3746 "enum.def" => enum_def_node = Some(cap.node),
3747 "trait.name" => trait_name_node = Some(cap.node),
3748 "trait.def" => trait_def_node = Some(cap.node),
3749 "fn.name" => fn_name_node = Some(cap.node),
3750 "fn.def" => fn_def_node = Some(cap.node),
3751 "val.name" => val_name_node = Some(cap.node),
3752 "val.def" => val_def_node = Some(cap.node),
3753 "var.name" => var_name_node = Some(cap.node),
3754 "var.def" => var_def_node = Some(cap.node),
3755 "given.name" => val_name_node = Some(cap.node),
3756 "given.def" => val_def_node = Some(cap.node),
3757 "type.name" => type_name_node = Some(cap.node),
3758 "type.def" => type_def_node = Some(cap.node),
3759 _ => {}
3760 }
3761 }
3762
3763 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3764 symbols.push(Symbol {
3765 name: node_text(source, &name_node).to_string(),
3766 kind: SymbolKind::Class,
3767 range: node_range_with_decorators(&def_node, source, lang),
3768 signature: Some(extract_signature(source, &def_node)),
3769 scope_chain: scala_scope_chain(&def_node, source),
3770 exported: true,
3771 parent: scala_scope_chain(&def_node, source).last().cloned(),
3772 });
3773 }
3774
3775 if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
3776 symbols.push(Symbol {
3777 name: node_text(source, &name_node).to_string(),
3778 kind: SymbolKind::Class,
3779 range: node_range_with_decorators(&def_node, source, lang),
3780 signature: Some(extract_signature(source, &def_node)),
3781 scope_chain: scala_scope_chain(&def_node, source),
3782 exported: true,
3783 parent: scala_scope_chain(&def_node, source).last().cloned(),
3784 });
3785 }
3786
3787 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
3788 symbols.push(Symbol {
3789 name: node_text(source, &name_node).to_string(),
3790 kind: SymbolKind::Enum,
3791 range: node_range_with_decorators(&def_node, source, lang),
3792 signature: Some(extract_signature(source, &def_node)),
3793 scope_chain: scala_scope_chain(&def_node, source),
3794 exported: true,
3795 parent: scala_scope_chain(&def_node, source).last().cloned(),
3796 });
3797 }
3798
3799 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
3800 symbols.push(Symbol {
3801 name: node_text(source, &name_node).to_string(),
3802 kind: SymbolKind::Interface,
3803 range: node_range_with_decorators(&def_node, source, lang),
3804 signature: Some(extract_signature(source, &def_node)),
3805 scope_chain: scala_scope_chain(&def_node, source),
3806 exported: true,
3807 parent: scala_scope_chain(&def_node, source).last().cloned(),
3808 });
3809 }
3810
3811 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
3812 let scope_chain = scala_scope_chain(&def_node, source);
3813 let kind = if scope_chain.is_empty() {
3814 SymbolKind::Function
3815 } else {
3816 SymbolKind::Method
3817 };
3818 symbols.push(Symbol {
3819 name: node_text(source, &name_node).to_string(),
3820 kind,
3821 range: node_range_with_decorators(&def_node, source, lang),
3822 signature: Some(extract_signature(source, &def_node)),
3823 parent: scope_chain.last().cloned(),
3824 scope_chain,
3825 exported: true,
3826 });
3827 }
3828
3829 if let (Some(name_node), Some(def_node)) = (val_name_node, val_def_node) {
3830 let scope_chain = scala_scope_chain(&def_node, source);
3831 symbols.push(Symbol {
3832 name: node_text(source, &name_node).to_string(),
3833 kind: SymbolKind::Variable,
3834 range: node_range_with_decorators(&def_node, source, lang),
3835 signature: Some(extract_signature(source, &def_node)),
3836 parent: scope_chain.last().cloned(),
3837 scope_chain,
3838 exported: true,
3839 });
3840 }
3841
3842 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
3843 let scope_chain = scala_scope_chain(&def_node, source);
3844 symbols.push(Symbol {
3845 name: node_text(source, &name_node).to_string(),
3846 kind: SymbolKind::Variable,
3847 range: node_range_with_decorators(&def_node, source, lang),
3848 signature: Some(extract_signature(source, &def_node)),
3849 parent: scope_chain.last().cloned(),
3850 scope_chain,
3851 exported: true,
3852 });
3853 }
3854
3855 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
3856 let scope_chain = scala_scope_chain(&def_node, source);
3857 symbols.push(Symbol {
3858 name: node_text(source, &name_node).to_string(),
3859 kind: SymbolKind::TypeAlias,
3860 range: node_range_with_decorators(&def_node, source, lang),
3861 signature: Some(extract_signature(source, &def_node)),
3862 parent: scope_chain.last().cloned(),
3863 scope_chain,
3864 exported: true,
3865 });
3866 }
3867 }
3868
3869 dedup_symbols(&mut symbols);
3870 Ok(symbols)
3871}
3872
3873fn child_text_by_field_or_kind(
3874 node: &Node,
3875 source: &str,
3876 field_name: &str,
3877 kinds: &[&str],
3878) -> Option<String> {
3879 if let Some(name_node) = node.child_by_field_name(field_name) {
3880 return Some(node_text(source, &name_node).to_string());
3881 }
3882
3883 let mut cursor = node.walk();
3884 if !cursor.goto_first_child() {
3885 return None;
3886 }
3887
3888 loop {
3889 let child = cursor.node();
3890 if kinds.contains(&child.kind()) {
3891 return Some(node_text(source, &child).to_string());
3892 }
3893 if !cursor.goto_next_sibling() {
3894 break;
3895 }
3896 }
3897
3898 None
3899}
3900
3901fn push_captured_symbol(
3902 symbols: &mut Vec<Symbol>,
3903 source: &str,
3904 lang: LangId,
3905 name_node: Node,
3906 def_node: Node,
3907 kind: SymbolKind,
3908 scope_chain: Vec<String>,
3909 exported: bool,
3910) {
3911 symbols.push(Symbol {
3912 name: node_text(source, &name_node).to_string(),
3913 kind,
3914 range: node_range_with_decorators(&def_node, source, lang),
3915 signature: Some(extract_signature(source, &def_node)),
3916 parent: scope_chain.last().cloned(),
3917 scope_chain,
3918 exported,
3919 });
3920}
3921
3922fn java_scope_chain(node: &Node, source: &str) -> Vec<String> {
3923 let mut chain = Vec::new();
3924 let mut current = node.parent();
3925
3926 while let Some(parent) = current {
3927 if matches!(
3928 parent.kind(),
3929 "class_declaration"
3930 | "interface_declaration"
3931 | "annotation_type_declaration"
3932 | "enum_declaration"
3933 | "record_declaration"
3934 ) {
3935 if let Some(name_node) = parent.child_by_field_name("name") {
3936 chain.push(node_text(source, &name_node).to_string());
3937 }
3938 }
3939 current = parent.parent();
3940 }
3941
3942 chain.reverse();
3943 chain
3944}
3945
3946fn extract_java_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
3947 let lang = LangId::Java;
3948 let capture_names = query.capture_names();
3949 let mut symbols = Vec::new();
3950 let mut cursor = QueryCursor::new();
3951 let mut matches = cursor.matches(query, *root, source.as_bytes());
3952
3953 while let Some(m) = {
3954 matches.advance();
3955 matches.get()
3956 } {
3957 let mut class_name_node = None;
3958 let mut class_def_node = None;
3959 let mut interface_name_node = None;
3960 let mut interface_def_node = None;
3961 let mut enum_name_node = None;
3962 let mut enum_def_node = None;
3963 let mut struct_name_node = None;
3964 let mut struct_def_node = None;
3965 let mut fn_name_node = None;
3966 let mut fn_def_node = None;
3967 let mut var_name_node = None;
3968 let mut var_def_node = None;
3969
3970 for cap in m.captures {
3971 let Some(&name) = capture_names.get(cap.index as usize) else {
3972 continue;
3973 };
3974 match name {
3975 "class.name" => class_name_node = Some(cap.node),
3976 "class.def" => class_def_node = Some(cap.node),
3977 "interface.name" => interface_name_node = Some(cap.node),
3978 "interface.def" => interface_def_node = Some(cap.node),
3979 "enum.name" => enum_name_node = Some(cap.node),
3980 "enum.def" => enum_def_node = Some(cap.node),
3981 "struct.name" => struct_name_node = Some(cap.node),
3982 "struct.def" => struct_def_node = Some(cap.node),
3983 "fn.name" => fn_name_node = Some(cap.node),
3984 "fn.def" => fn_def_node = Some(cap.node),
3985 "var.name" => var_name_node = Some(cap.node),
3986 "var.def" => var_def_node = Some(cap.node),
3987 _ => {}
3988 }
3989 }
3990
3991 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
3992 push_captured_symbol(
3993 &mut symbols,
3994 source,
3995 lang,
3996 name_node,
3997 def_node,
3998 SymbolKind::Class,
3999 java_scope_chain(&def_node, source),
4000 true,
4001 );
4002 }
4003 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
4004 push_captured_symbol(
4005 &mut symbols,
4006 source,
4007 lang,
4008 name_node,
4009 def_node,
4010 SymbolKind::Interface,
4011 java_scope_chain(&def_node, source),
4012 true,
4013 );
4014 }
4015 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4016 push_captured_symbol(
4017 &mut symbols,
4018 source,
4019 lang,
4020 name_node,
4021 def_node,
4022 SymbolKind::Enum,
4023 java_scope_chain(&def_node, source),
4024 true,
4025 );
4026 }
4027 if let (Some(name_node), Some(def_node)) = (struct_name_node, struct_def_node) {
4028 push_captured_symbol(
4029 &mut symbols,
4030 source,
4031 lang,
4032 name_node,
4033 def_node,
4034 SymbolKind::Struct,
4035 java_scope_chain(&def_node, source),
4036 true,
4037 );
4038 }
4039 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4040 let scope_chain = java_scope_chain(&def_node, source);
4041 let kind = if scope_chain.is_empty() {
4042 SymbolKind::Function
4043 } else {
4044 SymbolKind::Method
4045 };
4046 push_captured_symbol(
4047 &mut symbols,
4048 source,
4049 lang,
4050 name_node,
4051 def_node,
4052 kind,
4053 scope_chain,
4054 true,
4055 );
4056 }
4057 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4058 push_captured_symbol(
4059 &mut symbols,
4060 source,
4061 lang,
4062 name_node,
4063 def_node,
4064 SymbolKind::Variable,
4065 java_scope_chain(&def_node, source),
4066 true,
4067 );
4068 }
4069 }
4070
4071 dedup_symbols(&mut symbols);
4072 Ok(symbols)
4073}
4074
4075fn ruby_scope_chain(node: &Node, source: &str) -> Vec<String> {
4076 let mut chain = Vec::new();
4077 let mut current = node.parent();
4078
4079 while let Some(parent) = current {
4080 if matches!(parent.kind(), "class" | "module") {
4081 if let Some(name_node) = parent.child_by_field_name("name") {
4082 chain.push(node_text(source, &name_node).to_string());
4083 }
4084 }
4085 current = parent.parent();
4086 }
4087
4088 chain.reverse();
4089 chain
4090}
4091
4092fn extract_ruby_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4093 let lang = LangId::Ruby;
4094 let capture_names = query.capture_names();
4095 let mut symbols = Vec::new();
4096 let mut cursor = QueryCursor::new();
4097 let mut matches = cursor.matches(query, *root, source.as_bytes());
4098
4099 while let Some(m) = {
4100 matches.advance();
4101 matches.get()
4102 } {
4103 let mut module_name_node = None;
4104 let mut module_def_node = None;
4105 let mut class_name_node = None;
4106 let mut class_def_node = None;
4107 let mut fn_name_node = None;
4108 let mut fn_def_node = None;
4109 let mut var_name_node = None;
4110 let mut var_def_node = None;
4111
4112 for cap in m.captures {
4113 let Some(&name) = capture_names.get(cap.index as usize) else {
4114 continue;
4115 };
4116 match name {
4117 "module.name" => module_name_node = Some(cap.node),
4118 "module.def" => module_def_node = Some(cap.node),
4119 "class.name" => class_name_node = Some(cap.node),
4120 "class.def" => class_def_node = Some(cap.node),
4121 "fn.name" => fn_name_node = Some(cap.node),
4122 "fn.def" => fn_def_node = Some(cap.node),
4123 "var.name" => var_name_node = Some(cap.node),
4124 "var.def" => var_def_node = Some(cap.node),
4125 _ => {}
4126 }
4127 }
4128
4129 if let (Some(name_node), Some(def_node)) = (module_name_node, module_def_node) {
4130 push_captured_symbol(
4131 &mut symbols,
4132 source,
4133 lang,
4134 name_node,
4135 def_node,
4136 SymbolKind::Class,
4137 ruby_scope_chain(&def_node, source),
4138 true,
4139 );
4140 }
4141 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4142 push_captured_symbol(
4143 &mut symbols,
4144 source,
4145 lang,
4146 name_node,
4147 def_node,
4148 SymbolKind::Class,
4149 ruby_scope_chain(&def_node, source),
4150 true,
4151 );
4152 }
4153 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4154 let scope_chain = ruby_scope_chain(&def_node, source);
4155 let kind = if scope_chain.is_empty() {
4156 SymbolKind::Function
4157 } else {
4158 SymbolKind::Method
4159 };
4160 push_captured_symbol(
4161 &mut symbols,
4162 source,
4163 lang,
4164 name_node,
4165 def_node,
4166 kind,
4167 scope_chain,
4168 true,
4169 );
4170 }
4171 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4172 push_captured_symbol(
4173 &mut symbols,
4174 source,
4175 lang,
4176 name_node,
4177 def_node,
4178 SymbolKind::Variable,
4179 ruby_scope_chain(&def_node, source),
4180 true,
4181 );
4182 }
4183 }
4184
4185 dedup_symbols(&mut symbols);
4186 Ok(symbols)
4187}
4188
4189fn kotlin_scope_chain(node: &Node, source: &str) -> Vec<String> {
4190 let mut chain = Vec::new();
4191 let mut current = node.parent();
4192
4193 while let Some(parent) = current {
4194 if matches!(parent.kind(), "class_declaration" | "object_declaration") {
4195 if let Some(name) =
4196 child_text_by_field_or_kind(&parent, source, "name", &["type_identifier"])
4197 {
4198 chain.push(name);
4199 }
4200 }
4201 current = parent.parent();
4202 }
4203
4204 chain.reverse();
4205 chain
4206}
4207
4208fn extract_kotlin_symbols(
4209 source: &str,
4210 root: &Node,
4211 query: &Query,
4212) -> Result<Vec<Symbol>, AftError> {
4213 let lang = LangId::Kotlin;
4214 let capture_names = query.capture_names();
4215 let mut symbols = Vec::new();
4216 let mut cursor = QueryCursor::new();
4217 let mut matches = cursor.matches(query, *root, source.as_bytes());
4218
4219 while let Some(m) = {
4220 matches.advance();
4221 matches.get()
4222 } {
4223 let mut class_name_node = None;
4224 let mut class_def_node = None;
4225 let mut object_name_node = None;
4226 let mut object_def_node = None;
4227 let mut fn_name_node = None;
4228 let mut fn_def_node = None;
4229 let mut var_name_node = None;
4230 let mut var_def_node = None;
4231 let mut type_name_node = None;
4232 let mut type_def_node = None;
4233
4234 for cap in m.captures {
4235 let Some(&name) = capture_names.get(cap.index as usize) else {
4236 continue;
4237 };
4238 match name {
4239 "class.name" => class_name_node = Some(cap.node),
4240 "class.def" => class_def_node = Some(cap.node),
4241 "object.name" => object_name_node = Some(cap.node),
4242 "object.def" => object_def_node = Some(cap.node),
4243 "fn.name" => fn_name_node = Some(cap.node),
4244 "fn.def" => fn_def_node = Some(cap.node),
4245 "var.name" => var_name_node = Some(cap.node),
4246 "var.def" => var_def_node = Some(cap.node),
4247 "type.name" => type_name_node = Some(cap.node),
4248 "type.def" => type_def_node = Some(cap.node),
4249 _ => {}
4250 }
4251 }
4252
4253 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4254 push_captured_symbol(
4255 &mut symbols,
4256 source,
4257 lang,
4258 name_node,
4259 def_node,
4260 SymbolKind::Class,
4261 kotlin_scope_chain(&def_node, source),
4262 true,
4263 );
4264 }
4265 if let (Some(name_node), Some(def_node)) = (object_name_node, object_def_node) {
4266 push_captured_symbol(
4267 &mut symbols,
4268 source,
4269 lang,
4270 name_node,
4271 def_node,
4272 SymbolKind::Class,
4273 kotlin_scope_chain(&def_node, source),
4274 true,
4275 );
4276 }
4277 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4278 let scope_chain = kotlin_scope_chain(&def_node, source);
4279 let kind = if scope_chain.is_empty() {
4280 SymbolKind::Function
4281 } else {
4282 SymbolKind::Method
4283 };
4284 push_captured_symbol(
4285 &mut symbols,
4286 source,
4287 lang,
4288 name_node,
4289 def_node,
4290 kind,
4291 scope_chain,
4292 true,
4293 );
4294 }
4295 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4296 push_captured_symbol(
4297 &mut symbols,
4298 source,
4299 lang,
4300 name_node,
4301 def_node,
4302 SymbolKind::Variable,
4303 kotlin_scope_chain(&def_node, source),
4304 true,
4305 );
4306 }
4307 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
4308 push_captured_symbol(
4309 &mut symbols,
4310 source,
4311 lang,
4312 name_node,
4313 def_node,
4314 SymbolKind::TypeAlias,
4315 kotlin_scope_chain(&def_node, source),
4316 true,
4317 );
4318 }
4319 }
4320
4321 dedup_symbols(&mut symbols);
4322 Ok(symbols)
4323}
4324
4325fn swift_scope_chain(node: &Node, source: &str) -> Vec<String> {
4326 let mut chain = Vec::new();
4327 let mut current = node.parent();
4328
4329 while let Some(parent) = current {
4330 if matches!(parent.kind(), "class_declaration" | "protocol_declaration") {
4331 if let Some(name_node) = parent.child_by_field_name("name") {
4332 chain.push(node_text(source, &name_node).to_string());
4333 }
4334 }
4335 current = parent.parent();
4336 }
4337
4338 chain.reverse();
4339 chain
4340}
4341
4342fn swift_type_kind(source: &str, node: &Node) -> SymbolKind {
4343 match node
4344 .child_by_field_name("declaration_kind")
4345 .map(|kind_node| node_text(source, &kind_node))
4346 {
4347 Some("struct") => SymbolKind::Struct,
4348 Some("enum") => SymbolKind::Enum,
4349 _ => SymbolKind::Class,
4350 }
4351}
4352
4353fn extract_swift_symbols(
4354 source: &str,
4355 root: &Node,
4356 query: &Query,
4357) -> Result<Vec<Symbol>, AftError> {
4358 let lang = LangId::Swift;
4359 let capture_names = query.capture_names();
4360 let mut symbols = Vec::new();
4361 let mut cursor = QueryCursor::new();
4362 let mut matches = cursor.matches(query, *root, source.as_bytes());
4363
4364 while let Some(m) = {
4365 matches.advance();
4366 matches.get()
4367 } {
4368 let mut class_name_node = None;
4369 let mut class_def_node = None;
4370 let mut interface_name_node = None;
4371 let mut interface_def_node = None;
4372 let mut fn_name_node = None;
4373 let mut fn_def_node = None;
4374 let mut var_name_node = None;
4375 let mut var_def_node = None;
4376 let mut type_name_node = None;
4377 let mut type_def_node = None;
4378
4379 for cap in m.captures {
4380 let Some(&name) = capture_names.get(cap.index as usize) else {
4381 continue;
4382 };
4383 match name {
4384 "class.name" => class_name_node = Some(cap.node),
4385 "class.def" => class_def_node = Some(cap.node),
4386 "interface.name" => interface_name_node = Some(cap.node),
4387 "interface.def" => interface_def_node = Some(cap.node),
4388 "fn.name" => fn_name_node = Some(cap.node),
4389 "fn.def" => fn_def_node = Some(cap.node),
4390 "var.name" => var_name_node = Some(cap.node),
4391 "var.def" => var_def_node = Some(cap.node),
4392 "type.name" => type_name_node = Some(cap.node),
4393 "type.def" => type_def_node = Some(cap.node),
4394 _ => {}
4395 }
4396 }
4397
4398 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4399 let kind = swift_type_kind(source, &def_node);
4400 push_captured_symbol(
4401 &mut symbols,
4402 source,
4403 lang,
4404 name_node,
4405 def_node,
4406 kind,
4407 swift_scope_chain(&def_node, source),
4408 true,
4409 );
4410 }
4411 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
4412 push_captured_symbol(
4413 &mut symbols,
4414 source,
4415 lang,
4416 name_node,
4417 def_node,
4418 SymbolKind::Interface,
4419 swift_scope_chain(&def_node, source),
4420 true,
4421 );
4422 }
4423 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4424 let scope_chain = swift_scope_chain(&def_node, source);
4425 let kind = if scope_chain.is_empty() {
4426 SymbolKind::Function
4427 } else {
4428 SymbolKind::Method
4429 };
4430 push_captured_symbol(
4431 &mut symbols,
4432 source,
4433 lang,
4434 name_node,
4435 def_node,
4436 kind,
4437 scope_chain,
4438 true,
4439 );
4440 }
4441 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4442 push_captured_symbol(
4443 &mut symbols,
4444 source,
4445 lang,
4446 name_node,
4447 def_node,
4448 SymbolKind::Variable,
4449 swift_scope_chain(&def_node, source),
4450 true,
4451 );
4452 }
4453 if let (Some(name_node), Some(def_node)) = (type_name_node, type_def_node) {
4454 push_captured_symbol(
4455 &mut symbols,
4456 source,
4457 lang,
4458 name_node,
4459 def_node,
4460 SymbolKind::TypeAlias,
4461 swift_scope_chain(&def_node, source),
4462 true,
4463 );
4464 }
4465 }
4466
4467 dedup_symbols(&mut symbols);
4468 Ok(symbols)
4469}
4470
4471fn php_scope_chain(node: &Node, source: &str) -> Vec<String> {
4472 let mut chain = Vec::new();
4473 let mut current = node.parent();
4474
4475 while let Some(parent) = current {
4476 match parent.kind() {
4477 "namespace_definition"
4478 | "class_declaration"
4479 | "interface_declaration"
4480 | "trait_declaration"
4481 | "enum_declaration" => {
4482 if let Some(name_node) = parent.child_by_field_name("name") {
4483 chain.push(node_text(source, &name_node).to_string());
4484 }
4485 }
4486 _ => {}
4487 }
4488 current = parent.parent();
4489 }
4490
4491 chain.reverse();
4492 chain
4493}
4494
4495fn extract_php_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4496 let lang = LangId::Php;
4497 let capture_names = query.capture_names();
4498 let mut symbols = Vec::new();
4499 let mut cursor = QueryCursor::new();
4500 let mut matches = cursor.matches(query, *root, source.as_bytes());
4501
4502 while let Some(m) = {
4503 matches.advance();
4504 matches.get()
4505 } {
4506 let mut namespace_name_node = None;
4507 let mut namespace_def_node = None;
4508 let mut class_name_node = None;
4509 let mut class_def_node = None;
4510 let mut interface_name_node = None;
4511 let mut interface_def_node = None;
4512 let mut trait_name_node = None;
4513 let mut trait_def_node = None;
4514 let mut enum_name_node = None;
4515 let mut enum_def_node = None;
4516 let mut fn_name_node = None;
4517 let mut fn_def_node = None;
4518 let mut var_name_node = None;
4519 let mut var_def_node = None;
4520
4521 for cap in m.captures {
4522 let Some(&name) = capture_names.get(cap.index as usize) else {
4523 continue;
4524 };
4525 match name {
4526 "namespace.name" => namespace_name_node = Some(cap.node),
4527 "namespace.def" => namespace_def_node = Some(cap.node),
4528 "class.name" => class_name_node = Some(cap.node),
4529 "class.def" => class_def_node = Some(cap.node),
4530 "interface.name" => interface_name_node = Some(cap.node),
4531 "interface.def" => interface_def_node = Some(cap.node),
4532 "trait.name" => trait_name_node = Some(cap.node),
4533 "trait.def" => trait_def_node = Some(cap.node),
4534 "enum.name" => enum_name_node = Some(cap.node),
4535 "enum.def" => enum_def_node = Some(cap.node),
4536 "fn.name" => fn_name_node = Some(cap.node),
4537 "fn.def" => fn_def_node = Some(cap.node),
4538 "var.name" => var_name_node = Some(cap.node),
4539 "var.def" => var_def_node = Some(cap.node),
4540 _ => {}
4541 }
4542 }
4543
4544 if let (Some(name_node), Some(def_node)) = (namespace_name_node, namespace_def_node) {
4545 push_captured_symbol(
4546 &mut symbols,
4547 source,
4548 lang,
4549 name_node,
4550 def_node,
4551 SymbolKind::Class,
4552 php_scope_chain(&def_node, source),
4553 true,
4554 );
4555 }
4556 if let (Some(name_node), Some(def_node)) = (class_name_node, class_def_node) {
4557 push_captured_symbol(
4558 &mut symbols,
4559 source,
4560 lang,
4561 name_node,
4562 def_node,
4563 SymbolKind::Class,
4564 php_scope_chain(&def_node, source),
4565 true,
4566 );
4567 }
4568 if let (Some(name_node), Some(def_node)) = (interface_name_node, interface_def_node) {
4569 push_captured_symbol(
4570 &mut symbols,
4571 source,
4572 lang,
4573 name_node,
4574 def_node,
4575 SymbolKind::Interface,
4576 php_scope_chain(&def_node, source),
4577 true,
4578 );
4579 }
4580 if let (Some(name_node), Some(def_node)) = (trait_name_node, trait_def_node) {
4581 push_captured_symbol(
4582 &mut symbols,
4583 source,
4584 lang,
4585 name_node,
4586 def_node,
4587 SymbolKind::Interface,
4588 php_scope_chain(&def_node, source),
4589 true,
4590 );
4591 }
4592 if let (Some(name_node), Some(def_node)) = (enum_name_node, enum_def_node) {
4593 push_captured_symbol(
4594 &mut symbols,
4595 source,
4596 lang,
4597 name_node,
4598 def_node,
4599 SymbolKind::Enum,
4600 php_scope_chain(&def_node, source),
4601 true,
4602 );
4603 }
4604 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4605 let scope_chain = php_scope_chain(&def_node, source);
4606 let kind = if scope_chain.is_empty() || def_node.kind() == "function_definition" {
4607 SymbolKind::Function
4608 } else {
4609 SymbolKind::Method
4610 };
4611 push_captured_symbol(
4612 &mut symbols,
4613 source,
4614 lang,
4615 name_node,
4616 def_node,
4617 kind,
4618 scope_chain,
4619 true,
4620 );
4621 }
4622 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4623 push_captured_symbol(
4624 &mut symbols,
4625 source,
4626 lang,
4627 name_node,
4628 def_node,
4629 SymbolKind::Variable,
4630 php_scope_chain(&def_node, source),
4631 true,
4632 );
4633 }
4634 }
4635
4636 dedup_symbols(&mut symbols);
4637 Ok(symbols)
4638}
4639
4640fn lua_scope_chain(node: &Node, source: &str) -> Vec<String> {
4641 let mut chain = Vec::new();
4642
4643 if node.kind() == "function_declaration" {
4644 if let Some(name_node) = node.child_by_field_name("name") {
4645 match name_node.kind() {
4646 "dot_index_expression" | "method_index_expression" => {
4647 if let Some(table_node) = name_node.child_by_field_name("table") {
4648 chain.push(node_text(source, &table_node).to_string());
4649 }
4650 }
4651 _ => {}
4652 }
4653 }
4654 }
4655
4656 chain
4657}
4658
4659fn extract_lua_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4660 let lang = LangId::Lua;
4661 let capture_names = query.capture_names();
4662 let mut symbols = Vec::new();
4663 let mut cursor = QueryCursor::new();
4664 let mut matches = cursor.matches(query, *root, source.as_bytes());
4665
4666 while let Some(m) = {
4667 matches.advance();
4668 matches.get()
4669 } {
4670 let mut fn_name_node = None;
4671 let mut fn_def_node = None;
4672 let mut var_name_node = None;
4673 let mut var_def_node = None;
4674
4675 for cap in m.captures {
4676 let Some(&name) = capture_names.get(cap.index as usize) else {
4677 continue;
4678 };
4679 match name {
4680 "fn.name" => fn_name_node = Some(cap.node),
4681 "fn.def" => fn_def_node = Some(cap.node),
4682 "var.name" => var_name_node = Some(cap.node),
4683 "var.def" => var_def_node = Some(cap.node),
4684 _ => {}
4685 }
4686 }
4687
4688 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4689 let scope_chain = lua_scope_chain(&def_node, source);
4690 let kind = if scope_chain.is_empty() {
4691 SymbolKind::Function
4692 } else {
4693 SymbolKind::Method
4694 };
4695 push_captured_symbol(
4696 &mut symbols,
4697 source,
4698 lang,
4699 name_node,
4700 def_node,
4701 kind,
4702 scope_chain,
4703 true,
4704 );
4705 }
4706 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4707 push_captured_symbol(
4708 &mut symbols,
4709 source,
4710 lang,
4711 name_node,
4712 def_node,
4713 SymbolKind::Variable,
4714 vec![],
4715 true,
4716 );
4717 }
4718 }
4719
4720 dedup_symbols(&mut symbols);
4721 Ok(symbols)
4722}
4723
4724fn perl_package_name(source: &str, node: &Node) -> Option<String> {
4725 let mut cursor = node.walk();
4726 if !cursor.goto_first_child() {
4727 return None;
4728 }
4729
4730 loop {
4731 let child = cursor.node();
4732 if child.kind() == "package_name" {
4733 return Some(node_text(source, &child).to_string());
4734 }
4735 if !cursor.goto_next_sibling() {
4736 break;
4737 }
4738 }
4739
4740 None
4741}
4742
4743fn perl_scope_chain(node: &Node, source: &str) -> Vec<String> {
4744 let mut chain = Vec::new();
4745 let mut current = node.parent();
4746
4747 while let Some(parent) = current {
4748 if parent.kind() == "package_statement" {
4749 if let Some(name) = perl_package_name(source, &parent) {
4750 chain.push(name);
4751 }
4752 }
4753 current = parent.parent();
4754 }
4755
4756 if chain.is_empty() && node.kind() != "package_statement" {
4757 let mut sibling = node.prev_sibling();
4758 while let Some(prev) = sibling {
4759 if prev.kind() == "package_statement" {
4760 if let Some(name) = perl_package_name(source, &prev) {
4761 chain.push(name);
4762 }
4763 break;
4764 }
4765 sibling = prev.prev_sibling();
4766 }
4767 }
4768
4769 chain.reverse();
4770 chain
4771}
4772
4773fn extract_perl_symbols(source: &str, root: &Node, query: &Query) -> Result<Vec<Symbol>, AftError> {
4774 let lang = LangId::Perl;
4775 let capture_names = query.capture_names();
4776 let mut symbols = Vec::new();
4777 let mut cursor = QueryCursor::new();
4778 let mut matches = cursor.matches(query, *root, source.as_bytes());
4779
4780 while let Some(m) = {
4781 matches.advance();
4782 matches.get()
4783 } {
4784 let mut package_name_node = None;
4785 let mut package_def_node = None;
4786 let mut fn_name_node = None;
4787 let mut fn_def_node = None;
4788 let mut var_name_node = None;
4789 let mut var_def_node = None;
4790
4791 for cap in m.captures {
4792 let Some(&name) = capture_names.get(cap.index as usize) else {
4793 continue;
4794 };
4795 match name {
4796 "package.name" => package_name_node = Some(cap.node),
4797 "package.def" => package_def_node = Some(cap.node),
4798 "fn.name" => fn_name_node = Some(cap.node),
4799 "fn.def" => fn_def_node = Some(cap.node),
4800 "var.name" => var_name_node = Some(cap.node),
4801 "var.def" => var_def_node = Some(cap.node),
4802 _ => {}
4803 }
4804 }
4805
4806 if let (Some(name_node), Some(def_node)) = (package_name_node, package_def_node) {
4807 push_captured_symbol(
4808 &mut symbols,
4809 source,
4810 lang,
4811 name_node,
4812 def_node,
4813 SymbolKind::Class,
4814 vec![],
4815 true,
4816 );
4817 }
4818 if let (Some(name_node), Some(def_node)) = (fn_name_node, fn_def_node) {
4819 let scope_chain = perl_scope_chain(&def_node, source);
4820 let kind = if scope_chain.is_empty() {
4821 SymbolKind::Function
4822 } else {
4823 SymbolKind::Method
4824 };
4825 push_captured_symbol(
4826 &mut symbols,
4827 source,
4828 lang,
4829 name_node,
4830 def_node,
4831 kind,
4832 scope_chain,
4833 true,
4834 );
4835 }
4836 if let (Some(name_node), Some(def_node)) = (var_name_node, var_def_node) {
4837 push_captured_symbol(
4838 &mut symbols,
4839 source,
4840 lang,
4841 name_node,
4842 def_node,
4843 SymbolKind::Variable,
4844 perl_scope_chain(&def_node, source),
4845 true,
4846 );
4847 }
4848 }
4849
4850 dedup_symbols(&mut symbols);
4851 Ok(symbols)
4852}
4853
4854fn extract_vue_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
4855 let mut symbols = Vec::new();
4856 collect_vue_sections(source, root, &mut symbols, true);
4857 dedup_symbols(&mut symbols);
4858 Ok(symbols)
4859}
4860
4861fn collect_vue_sections(
4862 source: &str,
4863 node: &Node,
4864 symbols: &mut Vec<Symbol>,
4865 allow_sections: bool,
4866) {
4867 let mut cursor = node.walk();
4868 if !cursor.goto_first_child() {
4869 return;
4870 }
4871
4872 loop {
4873 let child = cursor.node();
4874 if let Some(section_name) = vue_section_name(&child) {
4875 if allow_sections {
4876 symbols.push(Symbol {
4877 name: section_name.to_string(),
4878 kind: SymbolKind::Heading,
4879 range: node_range(&child),
4880 signature: vue_opening_tag_signature(source, &child),
4881 scope_chain: vec![],
4882 exported: false,
4883 parent: None,
4884 });
4885 }
4886 } else {
4887 collect_vue_sections(
4888 source,
4889 &child,
4890 symbols,
4891 allow_sections && child.kind() == "document",
4892 );
4893 }
4894
4895 if !cursor.goto_next_sibling() {
4896 break;
4897 }
4898 }
4899}
4900
4901fn vue_section_name(node: &Node) -> Option<&'static str> {
4902 match node.kind() {
4903 "template_element" => Some("template"),
4904 "script_element" => Some("script"),
4905 "style_element" => Some("style"),
4906 _ => None,
4907 }
4908}
4909
4910fn vue_opening_tag_signature(source: &str, node: &Node) -> Option<String> {
4911 find_child_by_kind(*node, "start_tag")
4912 .or_else(|| find_child_by_kind(*node, "script_start_tag"))
4913 .or_else(|| find_child_by_kind(*node, "style_start_tag"))
4914 .or_else(|| find_child_by_kind(*node, "template_start_tag"))
4915 .map(|tag| node_text(source, &tag).trim().to_string())
4916}
4917
4918fn extract_html_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
4919 let mut headings: Vec<(u8, Symbol)> = Vec::new();
4920 collect_html_headings(source, root, &mut headings);
4921
4922 let total_lines = source.lines().count() as u32;
4923
4924 for i in 0..headings.len() {
4928 let level = headings[i].0;
4929 let section_end = headings[i + 1..]
4930 .iter()
4931 .find(|(l, _)| *l <= level)
4932 .map(|(_, s)| s.range.start_line.saturating_sub(1))
4933 .unwrap_or_else(|| total_lines.saturating_sub(1));
4934 headings[i].1.range.end_line = section_end;
4935 if section_end != headings[i].1.range.start_line {
4936 headings[i].1.range.end_col = 0;
4937 }
4938 }
4939
4940 let mut scope_stack: Vec<(u8, String)> = Vec::new(); for (level, symbol) in headings.iter_mut() {
4943 while scope_stack.last().is_some_and(|(l, _)| *l >= *level) {
4945 scope_stack.pop();
4946 }
4947 symbol.scope_chain = scope_stack.iter().map(|(_, name)| name.clone()).collect();
4948 symbol.parent = scope_stack.last().map(|(_, name)| name.clone());
4949 scope_stack.push((*level, symbol.name.clone()));
4950 }
4951
4952 Ok(headings.into_iter().map(|(_, s)| s).collect())
4953}
4954
4955fn collect_html_headings(source: &str, node: &Node, headings: &mut Vec<(u8, Symbol)>) {
4957 let mut cursor = node.walk();
4958 if !cursor.goto_first_child() {
4959 return;
4960 }
4961
4962 loop {
4963 let child = cursor.node();
4964 if child.kind() == "element" {
4965 if let Some(start_tag) = child
4967 .child_by_field_name("start_tag")
4968 .or_else(|| child.child(0).filter(|c| c.kind() == "start_tag"))
4969 {
4970 if let Some(tag_name_node) = start_tag
4971 .child_by_field_name("tag_name")
4972 .or_else(|| start_tag.child(1).filter(|c| c.kind() == "tag_name"))
4973 {
4974 let tag_name = node_text(source, &tag_name_node).to_lowercase();
4975 if let Some(level) = match tag_name.as_str() {
4976 "h1" => Some(1u8),
4977 "h2" => Some(2),
4978 "h3" => Some(3),
4979 "h4" => Some(4),
4980 "h5" => Some(5),
4981 "h6" => Some(6),
4982 _ => None,
4983 } {
4984 let text = extract_element_text(source, &child).trim().to_string();
4986 if !text.is_empty() {
4987 let range = node_range(&child);
4988 let signature = format!("<h{}> {}", level, text);
4989 headings.push((
4990 level,
4991 Symbol {
4992 name: text,
4993 kind: SymbolKind::Heading,
4994 range,
4995 signature: Some(signature),
4996 scope_chain: vec![], exported: false,
4998 parent: None, },
5000 ));
5001 }
5002 }
5003 }
5004 }
5005 collect_html_headings(source, &child, headings);
5007 } else {
5008 collect_html_headings(source, &child, headings);
5010 }
5011
5012 if !cursor.goto_next_sibling() {
5013 break;
5014 }
5015 }
5016}
5017
5018fn extract_element_text(source: &str, node: &Node) -> String {
5020 let mut text = String::new();
5021 let mut cursor = node.walk();
5022 if !cursor.goto_first_child() {
5023 return text;
5024 }
5025 loop {
5026 let child = cursor.node();
5027 match child.kind() {
5028 "text" => {
5029 text.push_str(node_text(source, &child));
5030 }
5031 "element" => {
5032 text.push_str(&extract_element_text(source, &child));
5034 }
5035 _ => {}
5036 }
5037 if !cursor.goto_next_sibling() {
5038 break;
5039 }
5040 }
5041 text
5042}
5043
5044fn extract_md_symbols(source: &str, root: &Node) -> Result<Vec<Symbol>, AftError> {
5048 let mut symbols = Vec::new();
5049 extract_md_sections(source, root, &mut symbols, &[]);
5050 Ok(symbols)
5051}
5052
5053fn extract_md_sections(
5055 source: &str,
5056 node: &Node,
5057 symbols: &mut Vec<Symbol>,
5058 scope_chain: &[String],
5059) {
5060 let mut cursor = node.walk();
5061 if !cursor.goto_first_child() {
5062 return;
5063 }
5064
5065 loop {
5066 let child = cursor.node();
5067 match child.kind() {
5068 "section" => {
5069 let mut section_cursor = child.walk();
5072 let mut heading_name = String::new();
5073 let mut heading_level: u8 = 0;
5074
5075 if section_cursor.goto_first_child() {
5076 loop {
5077 let section_child = section_cursor.node();
5078 if section_child.kind() == "atx_heading" {
5079 let mut h_cursor = section_child.walk();
5081 if h_cursor.goto_first_child() {
5082 loop {
5083 let h_child = h_cursor.node();
5084 let kind = h_child.kind();
5085 if kind.starts_with("atx_h") && kind.ends_with("_marker") {
5086 heading_level = kind
5088 .strip_prefix("atx_h")
5089 .and_then(|s| s.strip_suffix("_marker"))
5090 .and_then(|s| s.parse::<u8>().ok())
5091 .unwrap_or(1);
5092 } else if h_child.kind() == "inline" {
5093 heading_name =
5094 node_text(source, &h_child).trim().to_string();
5095 }
5096 if !h_cursor.goto_next_sibling() {
5097 break;
5098 }
5099 }
5100 }
5101 }
5102 if !section_cursor.goto_next_sibling() {
5103 break;
5104 }
5105 }
5106 }
5107
5108 if !heading_name.is_empty() {
5109 let range = node_range(&child);
5110 let signature = format!(
5111 "{} {}",
5112 "#".repeat((heading_level as usize).min(6)),
5113 heading_name
5114 );
5115
5116 symbols.push(Symbol {
5117 name: heading_name.clone(),
5118 kind: SymbolKind::Heading,
5119 range,
5120 signature: Some(signature),
5121 scope_chain: scope_chain.to_vec(),
5122 exported: false,
5123 parent: scope_chain.last().cloned(),
5124 });
5125
5126 let mut new_scope = scope_chain.to_vec();
5128 new_scope.push(heading_name);
5129 extract_md_sections(source, &child, symbols, &new_scope);
5130 }
5131 }
5132 _ => {}
5133 }
5134
5135 if !cursor.goto_next_sibling() {
5136 break;
5137 }
5138 }
5139}
5140
5141fn dedup_symbols(symbols: &mut Vec<Symbol>) {
5145 let mut seen = std::collections::HashSet::new();
5146 symbols.retain(|s| {
5147 let key = (s.name.clone(), format!("{:?}", s.kind), s.range.start_line);
5148 seen.insert(key)
5149 });
5150}
5151
5152pub struct TreeSitterProvider {
5155 parser: RefCell<FileParser>,
5156}
5157
5158#[derive(Debug, Clone)]
5159struct ReExportTarget {
5160 file: PathBuf,
5161 symbol_name: String,
5162}
5163
5164impl TreeSitterProvider {
5165 pub fn new() -> Self {
5167 Self::with_symbol_cache(Arc::new(RwLock::new(SymbolCache::new())))
5168 }
5169
5170 pub fn with_symbol_cache(symbol_cache: SharedSymbolCache) -> Self {
5172 Self {
5173 parser: RefCell::new(FileParser::with_symbol_cache(symbol_cache)),
5174 }
5175 }
5176
5177 pub fn symbol_cache_len(&self) -> usize {
5179 let parser = self.parser.borrow();
5180 parser.symbol_cache_len()
5181 }
5182
5183 pub fn symbol_cache(&self) -> SharedSymbolCache {
5185 let parser = self.parser.borrow();
5186 parser.symbol_cache()
5187 }
5188
5189 fn resolve_symbol_inner(
5190 &self,
5191 file: &Path,
5192 name: &str,
5193 depth: usize,
5194 visited: &mut HashSet<(PathBuf, String)>,
5195 ) -> Result<Vec<SymbolMatch>, AftError> {
5196 if depth > MAX_REEXPORT_DEPTH {
5197 return Ok(Vec::new());
5198 }
5199
5200 let canonical_file = std::fs::canonicalize(file).unwrap_or_else(|_| file.to_path_buf());
5201 if !visited.insert((canonical_file, name.to_string())) {
5202 return Ok(Vec::new());
5203 }
5204
5205 let symbols = self.parser.borrow_mut().extract_symbols(file)?;
5206 let local_matches = symbol_matches_in_file(file, &symbols, name);
5207 if !local_matches.is_empty() {
5208 return Ok(local_matches);
5209 }
5210
5211 if name == "default" {
5212 let default_matches = self.resolve_local_default_export(file, &symbols)?;
5213 if !default_matches.is_empty() {
5214 return Ok(default_matches);
5215 }
5216 }
5217
5218 let reexport_targets = self.collect_reexport_targets(file, name)?;
5219 let mut matches = Vec::new();
5220 let mut seen = HashSet::new();
5221 for target in reexport_targets {
5222 for resolved in
5223 self.resolve_symbol_inner(&target.file, &target.symbol_name, depth + 1, visited)?
5224 {
5225 let key = format!(
5226 "{}:{}:{}:{}:{}:{}",
5227 resolved.file,
5228 resolved.symbol.name,
5229 resolved.symbol.range.start_line,
5230 resolved.symbol.range.start_col,
5231 resolved.symbol.range.end_line,
5232 resolved.symbol.range.end_col
5233 );
5234 if seen.insert(key) {
5235 matches.push(resolved);
5236 }
5237 }
5238 }
5239
5240 Ok(matches)
5241 }
5242
5243 fn collect_reexport_targets(
5244 &self,
5245 file: &Path,
5246 requested_name: &str,
5247 ) -> Result<Vec<ReExportTarget>, AftError> {
5248 let (source, tree, lang) = self.read_parsed_file(file)?;
5249 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
5250 return Ok(Vec::new());
5251 }
5252
5253 let mut targets = Vec::new();
5254 let root = tree.root_node();
5255 let from_dir = file.parent().unwrap_or_else(|| Path::new("."));
5256
5257 let mut cursor = root.walk();
5258 if !cursor.goto_first_child() {
5259 return Ok(targets);
5260 }
5261
5262 loop {
5263 let node = cursor.node();
5264 if node.kind() == "export_statement" {
5265 let Some(source_node) = node.child_by_field_name("source") else {
5266 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
5267 if let Some(symbol_name) =
5268 resolve_export_clause_name(&source, &export_clause, requested_name)
5269 {
5270 targets.push(ReExportTarget {
5271 file: file.to_path_buf(),
5272 symbol_name,
5273 });
5274 }
5275 }
5276 if !cursor.goto_next_sibling() {
5277 break;
5278 }
5279 continue;
5280 };
5281
5282 let Some(module_path) = string_content(&source, &source_node) else {
5283 if !cursor.goto_next_sibling() {
5284 break;
5285 }
5286 continue;
5287 };
5288
5289 let Some(target_file) = resolve_module_path(from_dir, &module_path) else {
5290 if !cursor.goto_next_sibling() {
5291 break;
5292 }
5293 continue;
5294 };
5295
5296 if let Some(export_clause) = find_child_by_kind(node, "export_clause") {
5297 if let Some(symbol_name) =
5298 resolve_export_clause_name(&source, &export_clause, requested_name)
5299 {
5300 targets.push(ReExportTarget {
5301 file: target_file,
5302 symbol_name,
5303 });
5304 }
5305 } else if export_statement_has_wildcard(&source, &node) {
5306 targets.push(ReExportTarget {
5307 file: target_file,
5308 symbol_name: requested_name.to_string(),
5309 });
5310 }
5311 }
5312
5313 if !cursor.goto_next_sibling() {
5314 break;
5315 }
5316 }
5317
5318 Ok(targets)
5319 }
5320
5321 fn resolve_local_default_export(
5322 &self,
5323 file: &Path,
5324 symbols: &[Symbol],
5325 ) -> Result<Vec<SymbolMatch>, AftError> {
5326 let (source, tree, lang) = self.read_parsed_file(file)?;
5327 if !matches!(lang, LangId::TypeScript | LangId::Tsx | LangId::JavaScript) {
5328 return Ok(Vec::new());
5329 }
5330
5331 let root = tree.root_node();
5332 let mut matches = Vec::new();
5333 let mut seen = HashSet::new();
5334
5335 let mut cursor = root.walk();
5336 if !cursor.goto_first_child() {
5337 return Ok(matches);
5338 }
5339
5340 loop {
5341 let node = cursor.node();
5342 if node.kind() == "export_statement"
5343 && node.child_by_field_name("source").is_none()
5344 && node_contains_token(&source, &node, "default")
5345 {
5346 if let Some(target_name) = default_export_target_name(&source, &node) {
5347 for symbol_match in symbol_matches_in_file(file, symbols, &target_name) {
5348 let key = format!(
5349 "{}:{}:{}:{}:{}:{}",
5350 symbol_match.file,
5351 symbol_match.symbol.name,
5352 symbol_match.symbol.range.start_line,
5353 symbol_match.symbol.range.start_col,
5354 symbol_match.symbol.range.end_line,
5355 symbol_match.symbol.range.end_col
5356 );
5357 if seen.insert(key) {
5358 matches.push(symbol_match);
5359 }
5360 }
5361 }
5362 }
5363
5364 if !cursor.goto_next_sibling() {
5365 break;
5366 }
5367 }
5368
5369 Ok(matches)
5370 }
5371
5372 fn read_parsed_file(&self, file: &Path) -> Result<(String, Tree, LangId), AftError> {
5373 let source = std::fs::read_to_string(file).map_err(|e| AftError::FileNotFound {
5374 path: format!("{}: {}", file.display(), e),
5375 })?;
5376 let (tree, lang) = {
5377 let mut parser = self.parser.borrow_mut();
5378 parser.parse_cloned(file)?
5379 };
5380 Ok((source, tree, lang))
5381 }
5382}
5383
5384fn symbol_matches_in_file(file: &Path, symbols: &[Symbol], name: &str) -> Vec<SymbolMatch> {
5385 symbols
5386 .iter()
5387 .filter(|symbol| symbol.name == name)
5388 .cloned()
5389 .map(|symbol| SymbolMatch {
5390 file: file.display().to_string(),
5391 symbol,
5392 })
5393 .collect()
5394}
5395
5396fn string_content(source: &str, node: &Node) -> Option<String> {
5397 let text = node_text(source, node);
5398 if text.len() < 2 {
5399 return None;
5400 }
5401
5402 Some(
5403 text.trim_start_matches(|c| c == '\'' || c == '"')
5404 .trim_end_matches(|c| c == '\'' || c == '"')
5405 .to_string(),
5406 )
5407}
5408
5409fn find_child_by_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
5410 let mut cursor = node.walk();
5411 if !cursor.goto_first_child() {
5412 return None;
5413 }
5414
5415 loop {
5416 let child = cursor.node();
5417 if child.kind() == kind {
5418 return Some(child);
5419 }
5420 if !cursor.goto_next_sibling() {
5421 break;
5422 }
5423 }
5424
5425 None
5426}
5427
5428fn resolve_export_clause_name(
5429 source: &str,
5430 export_clause: &Node,
5431 requested_name: &str,
5432) -> Option<String> {
5433 let mut cursor = export_clause.walk();
5434 if !cursor.goto_first_child() {
5435 return None;
5436 }
5437
5438 loop {
5439 let child = cursor.node();
5440 if child.kind() == "export_specifier" {
5441 let (source_name, exported_name) = export_specifier_names(source, &child)?;
5442 if exported_name == requested_name {
5443 return Some(source_name);
5444 }
5445 }
5446
5447 if !cursor.goto_next_sibling() {
5448 break;
5449 }
5450 }
5451
5452 None
5453}
5454
5455fn export_specifier_names(source: &str, specifier: &Node) -> Option<(String, String)> {
5456 let source_name = specifier
5457 .child_by_field_name("name")
5458 .map(|node| node_text(source, &node).to_string());
5459 let alias_name = specifier
5460 .child_by_field_name("alias")
5461 .map(|node| node_text(source, &node).to_string());
5462
5463 if let Some(source_name) = source_name {
5464 let exported_name = alias_name.unwrap_or_else(|| source_name.clone());
5465 return Some((source_name, exported_name));
5466 }
5467
5468 let mut names = Vec::new();
5469 let mut cursor = specifier.walk();
5470 if cursor.goto_first_child() {
5471 loop {
5472 let child = cursor.node();
5473 let child_text = node_text(source, &child).trim();
5474 if matches!(
5475 child.kind(),
5476 "identifier" | "type_identifier" | "property_identifier"
5477 ) || child_text == "default"
5478 {
5479 names.push(child_text.to_string());
5480 }
5481 if !cursor.goto_next_sibling() {
5482 break;
5483 }
5484 }
5485 }
5486
5487 match names.as_slice() {
5488 [name] => Some((name.clone(), name.clone())),
5489 [source_name, exported_name, ..] => Some((source_name.clone(), exported_name.clone())),
5490 _ => None,
5491 }
5492}
5493
5494fn export_statement_has_wildcard(source: &str, node: &Node) -> bool {
5495 let mut cursor = node.walk();
5496 if !cursor.goto_first_child() {
5497 return false;
5498 }
5499
5500 loop {
5501 if node_text(source, &cursor.node()).trim() == "*" {
5502 return true;
5503 }
5504 if !cursor.goto_next_sibling() {
5505 break;
5506 }
5507 }
5508
5509 false
5510}
5511
5512fn node_contains_token(source: &str, node: &Node, token: &str) -> bool {
5513 let mut cursor = node.walk();
5514 if !cursor.goto_first_child() {
5515 return false;
5516 }
5517
5518 loop {
5519 if node_text(source, &cursor.node()).trim() == token {
5520 return true;
5521 }
5522 if !cursor.goto_next_sibling() {
5523 break;
5524 }
5525 }
5526
5527 false
5528}
5529
5530fn default_export_target_name(source: &str, export_stmt: &Node) -> Option<String> {
5531 if let Some(value_node) = export_stmt.child_by_field_name("value") {
5532 if let Some(name) = default_export_node_name(source, &value_node) {
5533 return Some(name);
5534 }
5535 }
5536
5537 if let Some(declaration_node) = export_stmt.child_by_field_name("declaration") {
5538 if let Some(name) = default_export_node_name(source, &declaration_node) {
5539 return Some(name);
5540 }
5541 }
5542
5543 let mut cursor = export_stmt.walk();
5544 if !cursor.goto_first_child() {
5545 return None;
5546 }
5547
5548 loop {
5549 let child = cursor.node();
5550 if let Some(name) = default_export_node_name(source, &child) {
5551 return Some(name);
5552 }
5553
5554 if !cursor.goto_next_sibling() {
5555 break;
5556 }
5557 }
5558
5559 None
5560}
5561
5562fn default_export_node_name(source: &str, node: &Node) -> Option<String> {
5563 match node.kind() {
5564 "function_declaration"
5565 | "generator_function_declaration"
5566 | "function_expression"
5567 | "generator_function"
5568 | "class_declaration"
5569 | "class" => node
5570 .child_by_field_name("name")
5571 .map(|name_node| node_text(source, &name_node).to_string())
5572 .or_else(|| Some("default".to_string())),
5573 "interface_declaration" | "enum_declaration" | "type_alias_declaration" => node
5574 .child_by_field_name("name")
5575 .map(|name_node| node_text(source, &name_node).to_string()),
5576 "lexical_declaration" => lexical_declaration_name(source, node),
5577 "identifier" | "type_identifier" => {
5578 let text = node_text(source, node);
5579 (text != "export" && text != "default").then(|| text.to_string())
5580 }
5581 _ => None,
5582 }
5583}
5584
5585fn lexical_declaration_name(source: &str, node: &Node) -> Option<String> {
5586 let mut cursor = node.walk();
5587 if !cursor.goto_first_child() {
5588 return None;
5589 }
5590
5591 loop {
5592 let child = cursor.node();
5593 if child.kind() == "variable_declarator" {
5594 if let Some(name_node) = child.child_by_field_name("name") {
5595 return Some(node_text(source, &name_node).to_string());
5596 }
5597 }
5598 if !cursor.goto_next_sibling() {
5599 break;
5600 }
5601 }
5602
5603 None
5604}
5605
5606impl crate::language::LanguageProvider for TreeSitterProvider {
5607 fn resolve_symbol(&self, file: &Path, name: &str) -> Result<Vec<SymbolMatch>, AftError> {
5608 let matches = self.resolve_symbol_inner(file, name, 0, &mut HashSet::new())?;
5609
5610 if matches.is_empty() {
5611 Err(AftError::SymbolNotFound {
5612 name: name.to_string(),
5613 file: file.display().to_string(),
5614 })
5615 } else {
5616 Ok(matches)
5617 }
5618 }
5619
5620 fn list_symbols(&self, file: &Path) -> Result<Vec<Symbol>, AftError> {
5621 self.parser.borrow_mut().extract_symbols(file)
5622 }
5623
5624 fn as_any(&self) -> &dyn std::any::Any {
5625 self
5626 }
5627}
5628
5629#[cfg(test)]
5630mod tests {
5631 use super::*;
5632 use crate::language::LanguageProvider;
5633 use crate::symbol_cache_disk;
5634 use std::path::PathBuf;
5635
5636 fn fixture_path(name: &str) -> PathBuf {
5637 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
5638 .join("tests")
5639 .join("fixtures")
5640 .join(name)
5641 }
5642
5643 fn test_symbol(name: &str) -> Symbol {
5644 Symbol {
5645 name: name.to_string(),
5646 kind: SymbolKind::Function,
5647 range: Range {
5648 start_line: 0,
5649 start_col: 0,
5650 end_line: 0,
5651 end_col: 10,
5652 },
5653 signature: Some(format!("fn {name}()")),
5654 scope_chain: Vec::new(),
5655 exported: true,
5656 parent: None,
5657 }
5658 }
5659
5660 #[test]
5661 fn symbol_cache_load_from_disk_round_trips_synthetic_entry() {
5662 let project = tempfile::tempdir().expect("create project dir");
5663 let storage = tempfile::tempdir().expect("create storage dir");
5664 let source = project.path().join("src/lib.rs");
5665 std::fs::create_dir_all(source.parent().expect("source parent"))
5666 .expect("create source dir");
5667 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
5668 let mtime = std::fs::metadata(&source)
5669 .expect("stat source")
5670 .modified()
5671 .expect("source mtime");
5672 let content = std::fs::read(&source).expect("read source");
5673 let size = content.len() as u64;
5674 let hash = crate::cache_freshness::hash_bytes(&content);
5675
5676 let mut cache = SymbolCache::new();
5677 cache.set_project_root(project.path().to_path_buf());
5678 cache.insert(
5679 source.clone(),
5680 mtime,
5681 size,
5682 hash,
5683 vec![test_symbol("cached")],
5684 );
5685 symbol_cache_disk::write_to_disk(&cache, storage.path(), "unit-project")
5686 .expect("write symbol cache");
5687
5688 let mut restored = SymbolCache::new();
5689 let loaded = restored.load_from_disk(storage.path(), "unit-project", project.path());
5690 let symbols = restored.get(&source, mtime).expect("restored symbols");
5691
5692 assert_eq!(loaded, 1);
5693 assert_eq!(symbols.len(), 1);
5694 assert_eq!(symbols[0].name, "cached");
5695 }
5696
5697 #[test]
5698 fn symbol_cache_load_from_disk_drops_stale_synthetic_entry() {
5699 let project = tempfile::tempdir().expect("create project dir");
5700 let storage = tempfile::tempdir().expect("create storage dir");
5701 let source = project.path().join("src/lib.rs");
5702 std::fs::create_dir_all(source.parent().expect("source parent"))
5703 .expect("create source dir");
5704 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
5705 let mtime = std::fs::metadata(&source)
5706 .expect("stat source")
5707 .modified()
5708 .expect("source mtime");
5709 let content = std::fs::read(&source).expect("read source");
5710 let size = content.len() as u64;
5711 let hash = crate::cache_freshness::hash_bytes(&content);
5712
5713 let mut cache = SymbolCache::new();
5714 cache.set_project_root(project.path().to_path_buf());
5715 cache.insert(
5716 source.clone(),
5717 mtime,
5718 size,
5719 hash,
5720 vec![test_symbol("cached")],
5721 );
5722 symbol_cache_disk::write_to_disk(&cache, storage.path(), "stale-unit-project")
5723 .expect("write symbol cache");
5724
5725 std::fs::write(&source, "pub fn cached() {}\npub fn fresh() {}\n").expect("change source");
5726
5727 let mut restored = SymbolCache::new();
5728 let loaded = restored.load_from_disk(storage.path(), "stale-unit-project", project.path());
5729
5730 assert_eq!(loaded, 0);
5731 assert_eq!(restored.len(), 0);
5732 }
5733
5734 #[test]
5735 fn stale_prewarm_generation_cannot_repopulate_symbol_cache_after_reset() {
5736 let project = tempfile::tempdir().expect("create project dir");
5737 let storage = tempfile::tempdir().expect("create storage dir");
5738 let source = project.path().join("src/lib.rs");
5739 std::fs::create_dir_all(source.parent().expect("source parent"))
5740 .expect("create source dir");
5741 std::fs::write(&source, "pub fn cached() {}\n").expect("write source");
5742 let mtime = std::fs::metadata(&source)
5743 .expect("stat source")
5744 .modified()
5745 .expect("source mtime");
5746 let content = std::fs::read(&source).expect("read source");
5747 let size = content.len() as u64;
5748 let hash = crate::cache_freshness::hash_bytes(&content);
5749
5750 let mut disk_cache = SymbolCache::new();
5751 disk_cache.set_project_root(project.path().to_path_buf());
5752 disk_cache.insert(
5753 source.clone(),
5754 mtime,
5755 size,
5756 hash,
5757 vec![test_symbol("cached")],
5758 );
5759 symbol_cache_disk::write_to_disk(&disk_cache, storage.path(), "prewarm-reset")
5760 .expect("write symbol cache");
5761
5762 let shared = Arc::new(RwLock::new(SymbolCache::new()));
5763 let stale_generation = shared.write().unwrap().reset();
5764 let active_generation = shared.write().unwrap().reset();
5765 assert_ne!(stale_generation, active_generation);
5766
5767 {
5768 let mut cache = shared.write().unwrap();
5769 assert!(!cache
5770 .set_project_root_for_generation(stale_generation, project.path().to_path_buf()));
5771 assert_eq!(
5772 cache.load_from_disk_for_generation(
5773 stale_generation,
5774 storage.path(),
5775 "prewarm-reset",
5776 project.path()
5777 ),
5778 0
5779 );
5780 }
5781
5782 let mut stale_parser =
5783 FileParser::with_symbol_cache_generation(Arc::clone(&shared), Some(stale_generation));
5784 stale_parser
5785 .extract_symbols(&source)
5786 .expect("stale prewarm parses source but must not write cache");
5787
5788 let cache = shared.read().unwrap();
5789 assert_eq!(cache.generation(), active_generation);
5790 assert_eq!(cache.len(), 0);
5791 assert!(cache.project_root().is_none());
5792 assert!(!cache.contains_key(&source));
5793 }
5794
5795 #[test]
5798 fn detect_ts() {
5799 assert_eq!(
5800 detect_language(Path::new("foo.ts")),
5801 Some(LangId::TypeScript)
5802 );
5803 }
5804
5805 #[test]
5806 fn detect_tsx() {
5807 assert_eq!(detect_language(Path::new("foo.tsx")), Some(LangId::Tsx));
5808 }
5809
5810 #[test]
5811 fn detect_js() {
5812 assert_eq!(
5813 detect_language(Path::new("foo.js")),
5814 Some(LangId::JavaScript)
5815 );
5816 }
5817
5818 #[test]
5819 fn detect_jsx() {
5820 assert_eq!(
5821 detect_language(Path::new("foo.jsx")),
5822 Some(LangId::JavaScript)
5823 );
5824 }
5825
5826 #[test]
5827 fn detect_py() {
5828 assert_eq!(detect_language(Path::new("foo.py")), Some(LangId::Python));
5829 }
5830
5831 #[test]
5832 fn detect_rs() {
5833 assert_eq!(detect_language(Path::new("foo.rs")), Some(LangId::Rust));
5834 }
5835
5836 #[test]
5837 fn detect_go() {
5838 assert_eq!(detect_language(Path::new("foo.go")), Some(LangId::Go));
5839 }
5840
5841 #[test]
5842 fn detect_c() {
5843 assert_eq!(detect_language(Path::new("foo.c")), Some(LangId::C));
5844 }
5845
5846 #[test]
5847 fn detect_h() {
5848 assert_eq!(detect_language(Path::new("foo.h")), Some(LangId::C));
5849 }
5850
5851 #[test]
5852 fn detect_cc() {
5853 assert_eq!(detect_language(Path::new("foo.cc")), Some(LangId::Cpp));
5854 }
5855
5856 #[test]
5857 fn detect_cpp() {
5858 assert_eq!(detect_language(Path::new("foo.cpp")), Some(LangId::Cpp));
5859 }
5860
5861 #[test]
5862 fn detect_cxx() {
5863 assert_eq!(detect_language(Path::new("foo.cxx")), Some(LangId::Cpp));
5864 }
5865
5866 #[test]
5867 fn detect_hpp() {
5868 assert_eq!(detect_language(Path::new("foo.hpp")), Some(LangId::Cpp));
5869 }
5870
5871 #[test]
5872 fn detect_hh() {
5873 assert_eq!(detect_language(Path::new("foo.hh")), Some(LangId::Cpp));
5874 }
5875
5876 #[test]
5877 fn detect_zig() {
5878 assert_eq!(detect_language(Path::new("foo.zig")), Some(LangId::Zig));
5879 }
5880
5881 #[test]
5882 fn detect_cs() {
5883 assert_eq!(detect_language(Path::new("foo.cs")), Some(LangId::CSharp));
5884 }
5885
5886 #[test]
5887 fn detect_unknown_returns_none() {
5888 assert_eq!(detect_language(Path::new("foo.txt")), None);
5889 }
5890
5891 #[test]
5894 fn unsupported_extension_returns_invalid_request() {
5895 let path = fixture_path("sample.ts");
5897 let bad_path = path.with_extension("txt");
5898 std::fs::write(&bad_path, "hello").unwrap();
5900 let provider = TreeSitterProvider::new();
5901 let result = provider.list_symbols(&bad_path);
5902 std::fs::remove_file(&bad_path).ok();
5903 match result {
5904 Err(AftError::InvalidRequest { message }) => {
5905 assert!(
5906 message.contains("unsupported file extension"),
5907 "msg: {}",
5908 message
5909 );
5910 assert!(message.contains("txt"), "msg: {}", message);
5911 }
5912 other => panic!("expected InvalidRequest, got {:?}", other),
5913 }
5914 }
5915
5916 #[test]
5919 fn ts_extracts_all_symbol_kinds() {
5920 let provider = TreeSitterProvider::new();
5921 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5922
5923 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
5924 assert!(
5925 names.contains(&"greet"),
5926 "missing function greet: {:?}",
5927 names
5928 );
5929 assert!(names.contains(&"add"), "missing arrow fn add: {:?}", names);
5930 assert!(
5931 names.contains(&"UserService"),
5932 "missing class UserService: {:?}",
5933 names
5934 );
5935 assert!(
5936 names.contains(&"Config"),
5937 "missing interface Config: {:?}",
5938 names
5939 );
5940 assert!(
5941 names.contains(&"Status"),
5942 "missing enum Status: {:?}",
5943 names
5944 );
5945 assert!(
5946 names.contains(&"UserId"),
5947 "missing type alias UserId: {:?}",
5948 names
5949 );
5950 assert!(
5951 names.contains(&"internalHelper"),
5952 "missing non-exported fn: {:?}",
5953 names
5954 );
5955
5956 assert!(
5958 symbols.len() >= 6,
5959 "expected ≥6 symbols, got {}: {:?}",
5960 symbols.len(),
5961 names
5962 );
5963 }
5964
5965 #[test]
5966 fn ts_symbol_kinds_correct() {
5967 let provider = TreeSitterProvider::new();
5968 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5969
5970 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5971
5972 assert_eq!(find("greet").kind, SymbolKind::Function);
5973 assert_eq!(find("add").kind, SymbolKind::Function); assert_eq!(find("UserService").kind, SymbolKind::Class);
5975 assert_eq!(find("Config").kind, SymbolKind::Interface);
5976 assert_eq!(find("Status").kind, SymbolKind::Enum);
5977 assert_eq!(find("UserId").kind, SymbolKind::TypeAlias);
5978 }
5979
5980 #[test]
5981 fn ts_export_detection() {
5982 let provider = TreeSitterProvider::new();
5983 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
5984
5985 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
5986
5987 assert!(find("greet").exported, "greet should be exported");
5988 assert!(find("add").exported, "add should be exported");
5989 assert!(
5990 find("UserService").exported,
5991 "UserService should be exported"
5992 );
5993 assert!(find("Config").exported, "Config should be exported");
5994 assert!(find("Status").exported, "Status should be exported");
5995 assert!(
5996 !find("internalHelper").exported,
5997 "internalHelper should not be exported"
5998 );
5999 }
6000
6001 #[test]
6002 fn ts_method_scope_chain() {
6003 let provider = TreeSitterProvider::new();
6004 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
6005
6006 let methods: Vec<&Symbol> = symbols
6007 .iter()
6008 .filter(|s| s.kind == SymbolKind::Method)
6009 .collect();
6010 assert!(!methods.is_empty(), "should have at least one method");
6011
6012 for method in &methods {
6013 assert_eq!(
6014 method.scope_chain,
6015 vec!["UserService"],
6016 "method {} should have UserService in scope chain",
6017 method.name
6018 );
6019 assert_eq!(method.parent.as_deref(), Some("UserService"));
6020 }
6021 }
6022
6023 #[test]
6024 fn ts_signatures_present() {
6025 let provider = TreeSitterProvider::new();
6026 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
6027
6028 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6029
6030 let greet_sig = find("greet").signature.as_ref().unwrap();
6031 assert!(
6032 greet_sig.contains("greet"),
6033 "signature should contain function name: {}",
6034 greet_sig
6035 );
6036 }
6037
6038 #[test]
6039 fn ts_ranges_valid() {
6040 let provider = TreeSitterProvider::new();
6041 let symbols = provider.list_symbols(&fixture_path("sample.ts")).unwrap();
6042
6043 for s in &symbols {
6044 assert!(
6045 s.range.end_line >= s.range.start_line,
6046 "symbol {} has invalid range: {:?}",
6047 s.name,
6048 s.range
6049 );
6050 }
6051 }
6052
6053 #[test]
6056 fn js_extracts_core_symbols() {
6057 let provider = TreeSitterProvider::new();
6058 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
6059
6060 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6061 assert!(
6062 names.contains(&"multiply"),
6063 "missing function multiply: {:?}",
6064 names
6065 );
6066 assert!(
6067 names.contains(&"divide"),
6068 "missing arrow fn divide: {:?}",
6069 names
6070 );
6071 assert!(
6072 names.contains(&"EventEmitter"),
6073 "missing class EventEmitter: {:?}",
6074 names
6075 );
6076 assert!(
6077 names.contains(&"main"),
6078 "missing default export fn main: {:?}",
6079 names
6080 );
6081
6082 assert!(
6083 symbols.len() >= 4,
6084 "expected ≥4 symbols, got {}: {:?}",
6085 symbols.len(),
6086 names
6087 );
6088 }
6089
6090 #[test]
6091 fn js_arrow_fn_correctly_named() {
6092 let provider = TreeSitterProvider::new();
6093 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
6094
6095 let divide = symbols.iter().find(|s| s.name == "divide").unwrap();
6096 assert_eq!(divide.kind, SymbolKind::Function);
6097 assert!(divide.exported, "divide should be exported");
6098
6099 let internal = symbols.iter().find(|s| s.name == "internalUtil").unwrap();
6100 assert_eq!(internal.kind, SymbolKind::Function);
6101 assert!(!internal.exported, "internalUtil should not be exported");
6102 }
6103
6104 #[test]
6105 fn js_method_scope_chain() {
6106 let provider = TreeSitterProvider::new();
6107 let symbols = provider.list_symbols(&fixture_path("sample.js")).unwrap();
6108
6109 let methods: Vec<&Symbol> = symbols
6110 .iter()
6111 .filter(|s| s.kind == SymbolKind::Method)
6112 .collect();
6113
6114 for method in &methods {
6115 assert_eq!(
6116 method.scope_chain,
6117 vec!["EventEmitter"],
6118 "method {} should have EventEmitter in scope chain",
6119 method.name
6120 );
6121 }
6122 }
6123
6124 #[test]
6127 fn tsx_extracts_react_component() {
6128 let provider = TreeSitterProvider::new();
6129 let symbols = provider.list_symbols(&fixture_path("sample.tsx")).unwrap();
6130
6131 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6132 assert!(
6133 names.contains(&"Button"),
6134 "missing React component Button: {:?}",
6135 names
6136 );
6137 assert!(
6138 names.contains(&"Counter"),
6139 "missing class Counter: {:?}",
6140 names
6141 );
6142 assert!(
6143 names.contains(&"formatLabel"),
6144 "missing function formatLabel: {:?}",
6145 names
6146 );
6147
6148 assert!(
6149 symbols.len() >= 2,
6150 "expected ≥2 symbols, got {}: {:?}",
6151 symbols.len(),
6152 names
6153 );
6154 }
6155
6156 #[test]
6157 fn tsx_jsx_doesnt_break_parser() {
6158 let provider = TreeSitterProvider::new();
6160 let result = provider.list_symbols(&fixture_path("sample.tsx"));
6161 assert!(
6162 result.is_ok(),
6163 "TSX parsing should succeed: {:?}",
6164 result.err()
6165 );
6166 }
6167
6168 #[test]
6171 fn resolve_symbol_finds_match() {
6172 let provider = TreeSitterProvider::new();
6173 let matches = provider
6174 .resolve_symbol(&fixture_path("sample.ts"), "greet")
6175 .unwrap();
6176 assert_eq!(matches.len(), 1);
6177 assert_eq!(matches[0].symbol.name, "greet");
6178 assert_eq!(matches[0].symbol.kind, SymbolKind::Function);
6179 }
6180
6181 #[test]
6182 fn resolve_symbol_not_found() {
6183 let provider = TreeSitterProvider::new();
6184 let result = provider.resolve_symbol(&fixture_path("sample.ts"), "nonexistent");
6185 assert!(matches!(result, Err(AftError::SymbolNotFound { .. })));
6186 }
6187
6188 #[test]
6189 fn resolve_symbol_follows_reexport_chains() {
6190 let dir = tempfile::tempdir().unwrap();
6191 let config = dir.path().join("config.ts");
6192 let barrel1 = dir.path().join("barrel1.ts");
6193 let barrel2 = dir.path().join("barrel2.ts");
6194 let barrel3 = dir.path().join("barrel3.ts");
6195 let index = dir.path().join("index.ts");
6196
6197 std::fs::write(
6198 &config,
6199 "export class Config {}\nexport default class DefaultConfig {}\n",
6200 )
6201 .unwrap();
6202 std::fs::write(
6203 &barrel1,
6204 "export { Config } from './config';\nexport { default as NamedDefault } from './config';\n",
6205 )
6206 .unwrap();
6207 std::fs::write(
6208 &barrel2,
6209 "export { Config as RenamedConfig } from './barrel1';\n",
6210 )
6211 .unwrap();
6212 std::fs::write(
6213 &barrel3,
6214 "export * from './barrel2';\nexport * from './barrel1';\n",
6215 )
6216 .unwrap();
6217 std::fs::write(
6218 &index,
6219 "export class Config {}\nexport { RenamedConfig as FinalConfig } from './barrel3';\nexport * from './barrel3';\n",
6220 )
6221 .unwrap();
6222
6223 let provider = TreeSitterProvider::new();
6224 let config_canon = std::fs::canonicalize(&config).unwrap();
6225
6226 let direct = provider.resolve_symbol(&barrel1, "Config").unwrap();
6227 assert_eq!(direct.len(), 1);
6228 assert_eq!(direct[0].symbol.name, "Config");
6229 assert_eq!(direct[0].file, config_canon.display().to_string());
6230
6231 let renamed = provider.resolve_symbol(&barrel2, "RenamedConfig").unwrap();
6232 assert_eq!(renamed.len(), 1);
6233 assert_eq!(renamed[0].symbol.name, "Config");
6234 assert_eq!(renamed[0].file, config_canon.display().to_string());
6235
6236 let wildcard_chain = provider.resolve_symbol(&index, "FinalConfig").unwrap();
6237 assert_eq!(wildcard_chain.len(), 1);
6238 assert_eq!(wildcard_chain[0].symbol.name, "Config");
6239 assert_eq!(wildcard_chain[0].file, config_canon.display().to_string());
6240
6241 let wildcard_default = provider.resolve_symbol(&index, "NamedDefault").unwrap();
6242 assert_eq!(wildcard_default.len(), 1);
6243 assert_eq!(wildcard_default[0].symbol.name, "DefaultConfig");
6244 assert_eq!(wildcard_default[0].file, config_canon.display().to_string());
6245
6246 let local = provider.resolve_symbol(&index, "Config").unwrap();
6247 assert_eq!(local.len(), 1);
6248 assert_eq!(local[0].symbol.name, "Config");
6249 assert_eq!(local[0].file, index.display().to_string());
6250 }
6251
6252 #[test]
6255 fn symbol_range_includes_rust_attributes() {
6256 let dir = tempfile::tempdir().unwrap();
6257 let path = dir.path().join("test_attrs.rs");
6258 std::fs::write(
6259 &path,
6260 "/// This is a doc comment\n#[test]\n#[cfg(test)]\nfn my_test_fn() {\n assert!(true);\n}\n",
6261 )
6262 .unwrap();
6263
6264 let provider = TreeSitterProvider::new();
6265 let matches = provider.resolve_symbol(&path, "my_test_fn").unwrap();
6266 assert_eq!(matches.len(), 1);
6267 assert_eq!(
6268 matches[0].symbol.range.start_line, 0,
6269 "symbol range should include preceding /// doc comment, got start_line={}",
6270 matches[0].symbol.range.start_line
6271 );
6272 }
6273
6274 #[test]
6275 fn symbol_range_includes_go_doc_comment() {
6276 let dir = tempfile::tempdir().unwrap();
6277 let path = dir.path().join("test_doc.go");
6278 std::fs::write(
6279 &path,
6280 "package main\n\n// MyFunc does something useful.\n// It has a multi-line doc.\nfunc MyFunc() {\n}\n",
6281 )
6282 .unwrap();
6283
6284 let provider = TreeSitterProvider::new();
6285 let matches = provider.resolve_symbol(&path, "MyFunc").unwrap();
6286 assert_eq!(matches.len(), 1);
6287 assert_eq!(
6288 matches[0].symbol.range.start_line, 2,
6289 "symbol range should include preceding doc comments, got start_line={}",
6290 matches[0].symbol.range.start_line
6291 );
6292 }
6293
6294 #[test]
6295 fn symbol_range_skips_unrelated_comments() {
6296 let dir = tempfile::tempdir().unwrap();
6297 let path = dir.path().join("test_gap.go");
6298 std::fs::write(
6299 &path,
6300 "package main\n\n// This is a standalone comment\n\nfunc Standalone() {\n}\n",
6301 )
6302 .unwrap();
6303
6304 let provider = TreeSitterProvider::new();
6305 let matches = provider.resolve_symbol(&path, "Standalone").unwrap();
6306 assert_eq!(matches.len(), 1);
6307 assert_eq!(
6308 matches[0].symbol.range.start_line, 4,
6309 "symbol range should NOT include comment separated by blank line, got start_line={}",
6310 matches[0].symbol.range.start_line
6311 );
6312 }
6313
6314 #[test]
6315 fn parse_cache_returns_same_tree() {
6316 let mut parser = FileParser::new();
6317 let path = fixture_path("sample.ts");
6318
6319 let (tree1, _) = parser.parse(&path).unwrap();
6320 let tree1_root = tree1.root_node().byte_range();
6321
6322 let (tree2, _) = parser.parse(&path).unwrap();
6323 let tree2_root = tree2.root_node().byte_range();
6324
6325 assert_eq!(tree1_root, tree2_root);
6327 }
6328
6329 #[test]
6330 fn extract_symbols_from_tree_matches_list_symbols() {
6331 let path = fixture_path("sample.rs");
6332 let source = std::fs::read_to_string(&path).unwrap();
6333
6334 let provider = TreeSitterProvider::new();
6335 let listed = provider.list_symbols(&path).unwrap();
6336
6337 let mut parser = FileParser::new();
6338 let (tree, lang) = parser.parse(&path).unwrap();
6339 let extracted = extract_symbols_from_tree(&source, tree, lang).unwrap();
6340
6341 assert_eq!(symbols_as_debug(&extracted), symbols_as_debug(&listed));
6342 }
6343
6344 fn symbols_as_debug(symbols: &[Symbol]) -> Vec<String> {
6345 symbols
6346 .iter()
6347 .map(|symbol| {
6348 format!(
6349 "{}|{:?}|{}:{}-{}:{}|{:?}|{:?}|{}|{:?}",
6350 symbol.name,
6351 symbol.kind,
6352 symbol.range.start_line,
6353 symbol.range.start_col,
6354 symbol.range.end_line,
6355 symbol.range.end_col,
6356 symbol.signature,
6357 symbol.scope_chain,
6358 symbol.exported,
6359 symbol.parent,
6360 )
6361 })
6362 .collect()
6363 }
6364
6365 #[test]
6368 fn py_extracts_all_symbols() {
6369 let provider = TreeSitterProvider::new();
6370 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6371
6372 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6373 assert!(
6374 names.contains(&"top_level_function"),
6375 "missing top_level_function: {:?}",
6376 names
6377 );
6378 assert!(names.contains(&"MyClass"), "missing MyClass: {:?}", names);
6379 assert!(
6380 names.contains(&"instance_method"),
6381 "missing method instance_method: {:?}",
6382 names
6383 );
6384 assert!(
6385 names.contains(&"decorated_function"),
6386 "missing decorated_function: {:?}",
6387 names
6388 );
6389
6390 assert!(
6392 symbols.len() >= 4,
6393 "expected ≥4 symbols, got {}: {:?}",
6394 symbols.len(),
6395 names
6396 );
6397 }
6398
6399 #[test]
6400 fn py_symbol_kinds_correct() {
6401 let provider = TreeSitterProvider::new();
6402 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6403
6404 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6405
6406 assert_eq!(find("top_level_function").kind, SymbolKind::Function);
6407 assert_eq!(find("MyClass").kind, SymbolKind::Class);
6408 assert_eq!(find("instance_method").kind, SymbolKind::Method);
6409 assert_eq!(find("decorated_function").kind, SymbolKind::Function);
6410 assert_eq!(find("OuterClass").kind, SymbolKind::Class);
6411 assert_eq!(find("InnerClass").kind, SymbolKind::Class);
6412 assert_eq!(find("inner_method").kind, SymbolKind::Method);
6413 assert_eq!(find("outer_method").kind, SymbolKind::Method);
6414 }
6415
6416 #[test]
6417 fn py_method_scope_chain() {
6418 let provider = TreeSitterProvider::new();
6419 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6420
6421 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6422
6423 assert_eq!(
6425 find("instance_method").scope_chain,
6426 vec!["MyClass"],
6427 "instance_method should have MyClass in scope chain"
6428 );
6429 assert_eq!(find("instance_method").parent.as_deref(), Some("MyClass"));
6430
6431 assert_eq!(
6433 find("inner_method").scope_chain,
6434 vec!["OuterClass", "InnerClass"],
6435 "inner_method should have nested scope chain"
6436 );
6437
6438 assert_eq!(
6440 find("InnerClass").scope_chain,
6441 vec!["OuterClass"],
6442 "InnerClass should have OuterClass in scope"
6443 );
6444
6445 assert!(
6447 find("top_level_function").scope_chain.is_empty(),
6448 "top-level function should have empty scope chain"
6449 );
6450 }
6451
6452 #[test]
6453 fn py_decorated_function_signature() {
6454 let provider = TreeSitterProvider::new();
6455 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6456
6457 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6458
6459 let sig = find("decorated_function").signature.as_ref().unwrap();
6460 assert!(
6461 sig.contains("@staticmethod"),
6462 "decorated function signature should include decorator: {}",
6463 sig
6464 );
6465 assert!(
6466 sig.contains("def decorated_function"),
6467 "signature should include function def: {}",
6468 sig
6469 );
6470 }
6471
6472 #[test]
6473 fn py_ranges_valid() {
6474 let provider = TreeSitterProvider::new();
6475 let symbols = provider.list_symbols(&fixture_path("sample.py")).unwrap();
6476
6477 for s in &symbols {
6478 assert!(
6479 s.range.end_line >= s.range.start_line,
6480 "symbol {} has invalid range: {:?}",
6481 s.name,
6482 s.range
6483 );
6484 }
6485 }
6486
6487 #[test]
6490 fn rs_extracts_all_symbols() {
6491 let provider = TreeSitterProvider::new();
6492 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6493
6494 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6495 assert!(
6496 names.contains(&"public_function"),
6497 "missing public_function: {:?}",
6498 names
6499 );
6500 assert!(
6501 names.contains(&"private_function"),
6502 "missing private_function: {:?}",
6503 names
6504 );
6505 assert!(names.contains(&"MyStruct"), "missing MyStruct: {:?}", names);
6506 assert!(names.contains(&"Color"), "missing enum Color: {:?}", names);
6507 assert!(
6508 names.contains(&"Drawable"),
6509 "missing trait Drawable: {:?}",
6510 names
6511 );
6512 assert!(
6514 names.contains(&"new"),
6515 "missing impl method new: {:?}",
6516 names
6517 );
6518
6519 assert!(
6521 symbols.len() >= 6,
6522 "expected ≥6 symbols, got {}: {:?}",
6523 symbols.len(),
6524 names
6525 );
6526 }
6527
6528 #[test]
6529 fn rs_symbol_kinds_correct() {
6530 let provider = TreeSitterProvider::new();
6531 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6532
6533 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6534
6535 assert_eq!(find("public_function").kind, SymbolKind::Function);
6536 assert_eq!(find("private_function").kind, SymbolKind::Function);
6537 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
6538 assert_eq!(find("Color").kind, SymbolKind::Enum);
6539 assert_eq!(find("Drawable").kind, SymbolKind::Interface); assert_eq!(find("new").kind, SymbolKind::Method);
6541 }
6542
6543 #[test]
6544 fn rs_pub_export_detection() {
6545 let provider = TreeSitterProvider::new();
6546 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6547
6548 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6549
6550 assert!(
6551 find("public_function").exported,
6552 "pub fn should be exported"
6553 );
6554 assert!(
6555 !find("private_function").exported,
6556 "non-pub fn should not be exported"
6557 );
6558 assert!(find("MyStruct").exported, "pub struct should be exported");
6559 assert!(find("Color").exported, "pub enum should be exported");
6560 assert!(find("Drawable").exported, "pub trait should be exported");
6561 assert!(
6562 find("new").exported,
6563 "pub fn inside impl should be exported"
6564 );
6565 assert!(
6566 !find("helper").exported,
6567 "non-pub fn inside impl should not be exported"
6568 );
6569 }
6570
6571 #[test]
6572 fn rs_impl_method_scope_chain() {
6573 let provider = TreeSitterProvider::new();
6574 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6575
6576 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6577
6578 assert_eq!(
6580 find("new").scope_chain,
6581 vec!["MyStruct"],
6582 "impl method should have type in scope chain"
6583 );
6584 assert_eq!(find("new").parent.as_deref(), Some("MyStruct"));
6585
6586 assert!(
6588 find("public_function").scope_chain.is_empty(),
6589 "free function should have empty scope chain"
6590 );
6591 }
6592
6593 #[test]
6594 fn rs_trait_impl_scope_chain() {
6595 let provider = TreeSitterProvider::new();
6596 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6597
6598 let draw = symbols.iter().find(|s| s.name == "draw").unwrap();
6600 assert_eq!(
6601 draw.scope_chain,
6602 vec!["Drawable for MyStruct"],
6603 "trait impl method should have 'Trait for Type' scope"
6604 );
6605 assert_eq!(draw.parent.as_deref(), Some("MyStruct"));
6606 }
6607
6608 #[test]
6609 fn rs_ranges_valid() {
6610 let provider = TreeSitterProvider::new();
6611 let symbols = provider.list_symbols(&fixture_path("sample.rs")).unwrap();
6612
6613 for s in &symbols {
6614 assert!(
6615 s.range.end_line >= s.range.start_line,
6616 "symbol {} has invalid range: {:?}",
6617 s.name,
6618 s.range
6619 );
6620 }
6621 }
6622
6623 #[test]
6626 fn go_extracts_all_symbols() {
6627 let provider = TreeSitterProvider::new();
6628 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6629
6630 let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
6631 assert!(
6632 names.contains(&"ExportedFunction"),
6633 "missing ExportedFunction: {:?}",
6634 names
6635 );
6636 assert!(
6637 names.contains(&"unexportedFunction"),
6638 "missing unexportedFunction: {:?}",
6639 names
6640 );
6641 assert!(
6642 names.contains(&"MyStruct"),
6643 "missing struct MyStruct: {:?}",
6644 names
6645 );
6646 assert!(
6647 names.contains(&"Reader"),
6648 "missing interface Reader: {:?}",
6649 names
6650 );
6651 assert!(
6653 names.contains(&"String"),
6654 "missing receiver method String: {:?}",
6655 names
6656 );
6657
6658 assert!(
6660 symbols.len() >= 4,
6661 "expected ≥4 symbols, got {}: {:?}",
6662 symbols.len(),
6663 names
6664 );
6665 }
6666
6667 #[test]
6668 fn go_symbol_kinds_correct() {
6669 let provider = TreeSitterProvider::new();
6670 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6671
6672 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6673
6674 assert_eq!(find("ExportedFunction").kind, SymbolKind::Function);
6675 assert_eq!(find("unexportedFunction").kind, SymbolKind::Function);
6676 assert_eq!(find("MyStruct").kind, SymbolKind::Struct);
6677 assert_eq!(find("Reader").kind, SymbolKind::Interface);
6678 assert_eq!(find("String").kind, SymbolKind::Method);
6679 assert_eq!(find("helper").kind, SymbolKind::Method);
6680 }
6681
6682 #[test]
6683 fn go_uppercase_export_detection() {
6684 let provider = TreeSitterProvider::new();
6685 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6686
6687 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6688
6689 assert!(
6690 find("ExportedFunction").exported,
6691 "ExportedFunction (uppercase) should be exported"
6692 );
6693 assert!(
6694 !find("unexportedFunction").exported,
6695 "unexportedFunction (lowercase) should not be exported"
6696 );
6697 assert!(
6698 find("MyStruct").exported,
6699 "MyStruct (uppercase) should be exported"
6700 );
6701 assert!(
6702 find("Reader").exported,
6703 "Reader (uppercase) should be exported"
6704 );
6705 assert!(
6706 find("String").exported,
6707 "String method (uppercase) should be exported"
6708 );
6709 assert!(
6710 !find("helper").exported,
6711 "helper method (lowercase) should not be exported"
6712 );
6713 }
6714
6715 #[test]
6716 fn go_receiver_method_scope_chain() {
6717 let provider = TreeSitterProvider::new();
6718 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6719
6720 let find = |name: &str| symbols.iter().find(|s| s.name == name).unwrap();
6721
6722 assert_eq!(
6724 find("String").scope_chain,
6725 vec!["MyStruct"],
6726 "receiver method should have type in scope chain"
6727 );
6728 assert_eq!(find("String").parent.as_deref(), Some("MyStruct"));
6729
6730 assert!(
6732 find("ExportedFunction").scope_chain.is_empty(),
6733 "regular function should have empty scope chain"
6734 );
6735 }
6736
6737 #[test]
6738 fn go_ranges_valid() {
6739 let provider = TreeSitterProvider::new();
6740 let symbols = provider.list_symbols(&fixture_path("sample.go")).unwrap();
6741
6742 for s in &symbols {
6743 assert!(
6744 s.range.end_line >= s.range.start_line,
6745 "symbol {} has invalid range: {:?}",
6746 s.name,
6747 s.range
6748 );
6749 }
6750 }
6751
6752 #[test]
6755 fn cross_language_all_six_produce_symbols() {
6756 let provider = TreeSitterProvider::new();
6757
6758 let fixtures = [
6759 ("sample.ts", "TypeScript"),
6760 ("sample.tsx", "TSX"),
6761 ("sample.js", "JavaScript"),
6762 ("sample.py", "Python"),
6763 ("sample.rs", "Rust"),
6764 ("sample.go", "Go"),
6765 ];
6766
6767 for (fixture, lang) in &fixtures {
6768 let symbols = provider
6769 .list_symbols(&fixture_path(fixture))
6770 .unwrap_or_else(|e| panic!("{} ({}) failed: {:?}", lang, fixture, e));
6771 assert!(
6772 symbols.len() >= 2,
6773 "{} should produce ≥2 symbols, got {}: {:?}",
6774 lang,
6775 symbols.len(),
6776 symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
6777 );
6778 }
6779 }
6780
6781 #[test]
6784 fn symbol_cache_returns_cached_results_on_second_call() {
6785 let dir = tempfile::tempdir().unwrap();
6786 let file = dir.path().join("test.rs");
6787 std::fs::write(&file, "pub fn hello() {}\npub fn world() {}").unwrap();
6788
6789 let mut parser = FileParser::new();
6790
6791 let symbols1 = parser.extract_symbols(&file).unwrap();
6792 assert_eq!(symbols1.len(), 2);
6793
6794 let symbols2 = parser.extract_symbols(&file).unwrap();
6796 assert_eq!(symbols2.len(), 2);
6797 assert_eq!(symbols1[0].name, symbols2[0].name);
6798
6799 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
6801 }
6802
6803 #[test]
6804 fn symbol_cache_invalidates_on_file_change() {
6805 let dir = tempfile::tempdir().unwrap();
6806 let file = dir.path().join("test.rs");
6807 std::fs::write(&file, "pub fn hello() {}").unwrap();
6808
6809 let mut parser = FileParser::new();
6810
6811 let symbols1 = parser.extract_symbols(&file).unwrap();
6812 assert_eq!(symbols1.len(), 1);
6813 assert_eq!(symbols1[0].name, "hello");
6814
6815 std::thread::sleep(std::time::Duration::from_millis(50));
6817
6818 std::fs::write(&file, "pub fn hello() {}\npub fn goodbye() {}").unwrap();
6820
6821 let symbols2 = parser.extract_symbols(&file).unwrap();
6823 assert_eq!(symbols2.len(), 2);
6824 assert!(symbols2.iter().any(|s| s.name == "goodbye"));
6825 }
6826
6827 #[test]
6828 fn symbol_cache_invalidate_method_clears_entry() {
6829 let dir = tempfile::tempdir().unwrap();
6830 let file = dir.path().join("test.rs");
6831 std::fs::write(&file, "pub fn hello() {}").unwrap();
6832
6833 let mut parser = FileParser::new();
6834 parser.extract_symbols(&file).unwrap();
6835 assert!(parser.symbol_cache.read().unwrap().contains_key(&file));
6836
6837 parser.invalidate_symbols(&file);
6838 assert!(!parser.symbol_cache.read().unwrap().contains_key(&file));
6839 assert!(!parser.cache.contains_key(&file));
6841 }
6842
6843 #[test]
6844 fn symbol_cache_works_for_multiple_languages() {
6845 let dir = tempfile::tempdir().unwrap();
6846 let rs_file = dir.path().join("lib.rs");
6847 let ts_file = dir.path().join("app.ts");
6848 let py_file = dir.path().join("main.py");
6849
6850 std::fs::write(&rs_file, "pub fn rust_fn() {}").unwrap();
6851 std::fs::write(&ts_file, "export function tsFn() {}").unwrap();
6852 std::fs::write(&py_file, "def py_fn():\n pass").unwrap();
6853
6854 let mut parser = FileParser::new();
6855
6856 let rs_syms = parser.extract_symbols(&rs_file).unwrap();
6857 let ts_syms = parser.extract_symbols(&ts_file).unwrap();
6858 let py_syms = parser.extract_symbols(&py_file).unwrap();
6859
6860 assert!(rs_syms.iter().any(|s| s.name == "rust_fn"));
6861 assert!(ts_syms.iter().any(|s| s.name == "tsFn"));
6862 assert!(py_syms.iter().any(|s| s.name == "py_fn"));
6863
6864 assert_eq!(parser.symbol_cache.read().unwrap().len(), 3);
6866
6867 let rs_syms2 = parser.extract_symbols(&rs_file).unwrap();
6869 assert_eq!(rs_syms.len(), rs_syms2.len());
6870 }
6871
6872 #[test]
6873 fn extract_json_symbols_top_level_keys() {
6874 let dir = tempfile::tempdir().unwrap();
6875 let file = dir.path().join("package.json");
6876 std::fs::write(&file, r#"{"name": "x", "version": "1"}"#).unwrap();
6877
6878 let mut parser = FileParser::new();
6879 let symbols = parser.extract_symbols(&file).unwrap();
6880
6881 assert_eq!(symbols.len(), 2);
6882 assert!(symbols
6883 .iter()
6884 .any(|s| s.name == "name" && s.kind == SymbolKind::Variable));
6885 assert!(symbols
6886 .iter()
6887 .any(|s| s.name == "version" && s.kind == SymbolKind::Variable));
6888 }
6889
6890 #[test]
6891 fn extract_json_symbols_root_array() {
6892 let dir = tempfile::tempdir().unwrap();
6893 let file = dir.path().join("array.json");
6894 std::fs::write(&file, "[1,2,3]").unwrap();
6895
6896 let mut parser = FileParser::new();
6897 let symbols = parser.extract_symbols(&file).unwrap();
6898
6899 assert_eq!(symbols.len(), 0);
6900 }
6901
6902 #[test]
6903 fn extract_json_symbols_no_recursion_into_nested() {
6904 let dir = tempfile::tempdir().unwrap();
6905 let file = dir.path().join("nested.json");
6906 std::fs::write(&file, r#"{"scripts": {"build": "tsc"}}"#).unwrap();
6907
6908 let mut parser = FileParser::new();
6909 let symbols = parser.extract_symbols(&file).unwrap();
6910
6911 assert_eq!(symbols.len(), 1);
6912 assert_eq!(symbols[0].name, "scripts");
6913 assert!(!symbols.iter().any(|s| s.name == "build"));
6914 }
6915
6916 #[test]
6917 fn extract_scala_symbols_object_and_method() {
6918 let dir = tempfile::tempdir().unwrap();
6919 let file = dir.path().join("Greeter.scala");
6920 std::fs::write(
6921 &file,
6922 "object Greeter {\n def hello(name: String): String = s\"hi $name\"\n}",
6923 )
6924 .unwrap();
6925
6926 let mut parser = FileParser::new();
6927 let symbols = parser.extract_symbols(&file).unwrap();
6928
6929 assert!(symbols
6930 .iter()
6931 .any(|s| s.name == "Greeter" && s.kind == SymbolKind::Class));
6932 assert!(symbols.iter().any(|s| s.name == "hello"
6933 && s.kind == SymbolKind::Method
6934 && s.scope_chain == vec!["Greeter".to_string()]));
6935 }
6936
6937 #[test]
6938 fn extract_scala_symbols_class_and_trait() {
6939 let dir = tempfile::tempdir().unwrap();
6940 let file = dir.path().join("Types.scala");
6941 std::fs::write(&file, "class Foo\ntrait Bar").unwrap();
6942
6943 let mut parser = FileParser::new();
6944 let symbols = parser.extract_symbols(&file).unwrap();
6945
6946 assert!(symbols
6947 .iter()
6948 .any(|s| s.name == "Foo" && s.kind == SymbolKind::Class));
6949 assert!(symbols
6950 .iter()
6951 .any(|s| s.name == "Bar" && s.kind == SymbolKind::Interface));
6952 }
6953}