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