use super::*;
use crate::ast::rewrite::replace_function_signature;
#[test]
fn check_no_overlapping_spans_ok() {
let spans = vec![(0, 3), (4, 7), (8, 10)];
let names = vec!["a", "b", "c"];
check_no_overlapping_spans(&spans, &names).unwrap();
}
#[test]
fn check_no_overlapping_spans_adjacent_ok() {
let spans = vec![(0, 3), (3, 6), (6, 9)];
let names = vec!["a", "b", "c"];
check_no_overlapping_spans(&spans, &names).unwrap();
}
#[test]
fn check_no_overlapping_spans_detects_overlap() {
let spans = vec![(0, 5), (3, 8)];
let names = vec!["foo", "bar"];
let err = check_no_overlapping_spans(&spans, &names).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("overlapping"), "error: {msg}");
assert!(
msg.contains("foo"),
"error should mention first symbol: {msg}"
);
assert!(
msg.contains("bar"),
"error should mention second symbol: {msg}"
);
}
#[test]
fn check_no_overlapping_spans_detects_containment() {
let spans = vec![(0, 10), (2, 5)];
let names = vec!["outer", "inner"];
let err = check_no_overlapping_spans(&spans, &names).unwrap_err();
assert!(err.to_string().contains("overlapping"));
}
#[test]
fn check_no_overlapping_spans_single_ok() {
let spans = vec![(0, 5)];
let names = vec!["only"];
check_no_overlapping_spans(&spans, &names).unwrap();
}
#[test]
fn check_no_overlapping_spans_empty_ok() {
check_no_overlapping_spans(&[], &[]).unwrap();
}
#[test]
fn extract_rust_symbols() {
let source = r#"
struct Foo {
x: i32,
}
fn bar() -> i32 {
42
}
impl Foo {
fn baz(&self) -> i32 {
self.x
}
}
"#;
let symbols = extract_symbols(source, Language::Rust);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"Foo"));
assert!(names.contains(&"bar"));
let impl_sym = symbols.iter().find(|s| s.kind == SymbolKind::Impl).unwrap();
assert_eq!(impl_sym.name, "Foo");
assert_eq!(impl_sym.children.len(), 1);
assert_eq!(impl_sym.children[0].name, "baz");
}
#[test]
fn rust_impl_trait_extracts_type_not_trait() {
let source = r#"
use std::fmt;
struct Foo;
impl fmt::Display for Foo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Foo")
}
}
"#;
let symbols = extract_symbols(source, Language::Rust);
let impl_sym = symbols.iter().find(|s| s.kind == SymbolKind::Impl).unwrap();
assert_eq!(
impl_sym.name, "Foo",
"impl target should be Foo, not the trait"
);
}
#[test]
fn rust_impl_without_trait() {
let source = "struct Bar;\nimpl Bar { fn go(&self) {} }\n";
let symbols = extract_symbols(source, Language::Rust);
let impl_sym = symbols.iter().find(|s| s.kind == SymbolKind::Impl).unwrap();
assert_eq!(impl_sym.name, "Bar");
}
#[test]
fn extract_python_symbols() {
let source = r#"
class MyClass:
def method(self):
pass
def standalone():
pass
"#;
let symbols = extract_symbols(source, Language::Python);
let class = symbols.iter().find(|s| s.name == "MyClass").unwrap();
assert_eq!(class.kind, SymbolKind::Class);
assert_eq!(class.children.len(), 1);
assert_eq!(class.children[0].name, "method");
assert_eq!(class.children[0].kind, SymbolKind::Method);
let func = symbols.iter().find(|s| s.name == "standalone").unwrap();
assert_eq!(func.kind, SymbolKind::Function);
}
#[test]
fn extract_python_decorated_method() {
let source = "class Foo:\n @staticmethod\n def bar():\n pass\n";
let symbols = extract_symbols(source, Language::Python);
let class = symbols.iter().find(|s| s.name == "Foo").unwrap();
assert_eq!(class.children.len(), 1);
assert_eq!(class.children[0].name, "bar");
assert_eq!(
class.children[0].kind,
SymbolKind::Method,
"decorated method should be classified as Method, not Function"
);
}
#[test]
fn extract_go_symbols() {
let source = r#"
package main
func main() {
fmt.Println("hello")
}
type Config struct {
Host string
}
"#;
let symbols = extract_symbols(source, Language::Go);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"main"));
assert!(names.contains(&"Config"));
}
#[test]
fn go_grouped_type_declaration() {
let source = "package main\n\ntype (\n\tPoint struct{ X, Y int }\n\tReader interface{ Read([]byte) (int, error) }\n\tAlias = int\n)\n";
let symbols = extract_symbols(source, Language::Go);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"Point"), "missing Point, got {:?}", names);
assert!(names.contains(&"Reader"), "missing Reader, got {:?}", names);
assert!(names.contains(&"Alias"), "missing Alias, got {:?}", names);
let point = symbols.iter().find(|s| s.name == "Point").unwrap();
assert_eq!(point.kind, SymbolKind::Struct);
let reader = symbols.iter().find(|s| s.name == "Reader").unwrap();
assert_eq!(reader.kind, SymbolKind::Interface);
}
#[test]
fn find_symbol_qualified() {
let source = r#"
impl Server {
fn start(&self) {}
fn stop(&self) {}
}
"#;
let symbols = extract_symbols(source, Language::Rust);
let found = find_symbol(&symbols, "Server::start").expect("should find Server::start");
assert_eq!(found.name, "start");
}
#[test]
fn find_symbol_unqualified_searches_children() {
let source = r#"
impl Server {
fn start(&self) {}
}
"#;
let symbols = extract_symbols(source, Language::Rust);
find_symbol(&symbols, "start").expect("should find 'start' via unqualified search");
}
#[test]
fn find_symbol_deeply_nested() {
let inner = SymbolDef {
name: "deep_method".into(),
kind: SymbolKind::Method,
start_line: 3,
end_line: 4,
signature: "fn deep_method()".into(),
children: Vec::new(),
depth: 2,
};
let mid = SymbolDef {
name: "MidStruct".into(),
kind: SymbolKind::Struct,
start_line: 2,
end_line: 5,
signature: "struct MidStruct".into(),
children: vec![inner],
depth: 1,
};
let outer = SymbolDef {
name: "outer_mod".into(),
kind: SymbolKind::Module,
start_line: 1,
end_line: 6,
signature: "mod outer_mod".into(),
children: vec![mid],
depth: 0,
};
let symbols = vec![outer];
find_symbol(&symbols, "deep_method").expect("should find deeply nested symbol");
find_symbol(&symbols, "outer_mod::MidStruct").expect("should find qualified nested symbol");
}
#[test]
fn find_symbol_qualified_skips_struct_to_impl() {
let source = r#"
struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
"#;
let symbols = extract_symbols(source, Language::Rust);
let found = find_symbol(&symbols, "Point::new").expect("should find Point::new via impl");
assert_eq!(found.name, "new");
}
#[test]
fn symbol_kind_from_str() {
assert_eq!(
SymbolKind::from_str_loose("function"),
Some(SymbolKind::Function)
);
assert_eq!(SymbolKind::from_str_loose("fn"), Some(SymbolKind::Function));
assert_eq!(
SymbolKind::from_str_loose("struct"),
Some(SymbolKind::Struct)
);
assert_eq!(SymbolKind::from_str_loose("CONST"), Some(SymbolKind::Const));
assert_eq!(SymbolKind::from_str_loose("unknown"), None);
}
#[test]
fn unknown_language_returns_empty() {
assert!(extract_symbols("anything", Language::Unknown).is_empty());
}
#[test]
fn signature_truncates_at_brace() {
let source = "fn hello(x: i32) {\n x + 1\n}\n";
let symbols = extract_symbols(source, Language::Rust);
assert_eq!(symbols[0].signature, "fn hello(x: i32)");
}
#[test]
fn replace_function_signature_basic() {
let src = "fn old(a: i32) -> i32 { a }\nfn other() {}";
let res = replace_function_signature(src, "old", "pub fn new(b: u32) -> u32");
let out = res.expect("replace_function_signature should succeed for matching name");
assert!(out.contains("pub fn new(b: u32) -> u32"));
assert!(out.contains("fn other"));
assert!(!out.contains("fn old"));
assert!(
out.contains("{ a }"),
"function body should be preserved: {out}"
);
}
#[test]
fn extract_typescript_symbols() {
let source = r#"
class Foo {
greet(name: string): string {
return `Hello, ${name}`;
}
farewell(): void {
console.log("bye");
}
}
function bar(x: number): number {
return x * 2;
}
interface Baz {
id: number;
name: string;
}
enum Status {
Active,
Inactive,
Pending,
}
const MAX_RETRIES = 5;
"#;
let symbols = extract_symbols(source, Language::TypeScript);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"Foo"), "should find class Foo");
assert!(names.contains(&"bar"), "should find function bar");
assert!(names.contains(&"Baz"), "should find interface Baz");
assert!(names.contains(&"Status"), "should find enum Status");
assert!(
names.contains(&"MAX_RETRIES"),
"should find const MAX_RETRIES"
);
let class_foo = symbols.iter().find(|s| s.name == "Foo").unwrap();
assert_eq!(class_foo.kind, SymbolKind::Class);
let child_names: Vec<&str> = class_foo.children.iter().map(|c| c.name.as_str()).collect();
assert!(
child_names.contains(&"greet"),
"Foo should contain method greet"
);
assert!(
child_names.contains(&"farewell"),
"Foo should contain method farewell"
);
let iface = symbols.iter().find(|s| s.name == "Baz").unwrap();
assert_eq!(iface.kind, SymbolKind::Interface);
let status = symbols.iter().find(|s| s.name == "Status").unwrap();
assert_eq!(status.kind, SymbolKind::Enum);
}
#[test]
fn extract_typescript_multi_declarator_const() {
let source = "const a = 1, b = 2, c = 3;\n";
let symbols = extract_symbols(source, Language::TypeScript);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"a"), "should find const a: {names:?}");
assert!(names.contains(&"b"), "should find const b: {names:?}");
assert!(names.contains(&"c"), "should find const c: {names:?}");
assert_eq!(
symbols.len(),
3,
"exactly 3 symbols for 3 declarators: {names:?}"
);
}
#[test]
fn extract_typescript_let_var_not_extracted() {
let source = "let x = 1, y = 2;\nvar z = 3;\n";
let symbols = extract_symbols(source, Language::TypeScript);
assert!(
symbols.is_empty(),
"let/var should not produce symbols: {:?}",
symbols.iter().map(|s| &s.name).collect::<Vec<_>>()
);
}
#[test]
fn extract_java_symbols() {
let source = r#"
public class Foo {
private int count;
public void bar() {
System.out.println("hello");
}
public int getCount() {
return count;
}
}
interface Baz {
void process();
String getName();
}
enum Status {
ACTIVE,
INACTIVE,
PENDING
}
"#;
let symbols = extract_symbols(source, Language::Java);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"Foo"), "should find class Foo");
assert!(names.contains(&"Baz"), "should find interface Baz");
assert!(names.contains(&"Status"), "should find enum Status");
let class_foo = symbols.iter().find(|s| s.name == "Foo").unwrap();
assert_eq!(class_foo.kind, SymbolKind::Class);
let child_names: Vec<&str> = class_foo.children.iter().map(|c| c.name.as_str()).collect();
assert!(
child_names.contains(&"bar"),
"Foo should contain method bar"
);
assert!(
child_names.contains(&"getCount"),
"Foo should contain method getCount"
);
let iface = symbols.iter().find(|s| s.name == "Baz").unwrap();
assert_eq!(iface.kind, SymbolKind::Interface);
let status = symbols.iter().find(|s| s.name == "Status").unwrap();
assert_eq!(status.kind, SymbolKind::Enum);
}
#[test]
fn extract_c_symbols() {
let source = r#"
#include <stdio.h>
void foo(int x) {
printf("%d\n", x);
}
int calculate(int a, int b) {
return a + b;
}
struct Bar {
int x;
int y;
char name[64];
};
enum Color {
RED,
GREEN,
BLUE
};
"#;
let symbols = extract_symbols(source, Language::C);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"foo"), "should find function foo");
assert!(
names.contains(&"calculate"),
"should find function calculate"
);
assert!(names.contains(&"Bar"), "should find struct Bar");
assert!(names.contains(&"Color"), "should find enum Color");
let foo_sym = symbols.iter().find(|s| s.name == "foo").unwrap();
assert_eq!(foo_sym.kind, SymbolKind::Function);
let bar_sym = symbols.iter().find(|s| s.name == "Bar").unwrap();
assert_eq!(bar_sym.kind, SymbolKind::Struct);
let color_sym = symbols.iter().find(|s| s.name == "Color").unwrap();
assert_eq!(color_sym.kind, SymbolKind::Enum);
}
#[test]
fn extract_c_pointer_returning_functions() {
let source = r#"
int *pointer_func(void) { return 0; }
void *void_ptr_func(void) { return 0; }
char *string_func(void) { return "hello"; }
typedef struct Foo { int x; } Foo;
Foo *foo_create(void) { return 0; }
"#;
let symbols = extract_symbols(source, Language::C);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(
names.contains(&"pointer_func"),
"should find int* function: {names:?}"
);
assert!(
names.contains(&"void_ptr_func"),
"should find void* function: {names:?}"
);
assert!(
names.contains(&"string_func"),
"should find char* function: {names:?}"
);
assert!(
names.contains(&"foo_create"),
"should find Foo* function (not mislabeled as 'Foo'): {names:?}"
);
for fname in &["pointer_func", "void_ptr_func", "string_func", "foo_create"] {
let sym = symbols.iter().find(|s| s.name == *fname).unwrap();
assert_eq!(
sym.kind,
SymbolKind::Function,
"{fname} should be Function, got {:?}",
sym.kind
);
}
}
#[test]
fn extract_cpp_symbols() {
let source = r#"
#include <iostream>
#include <string>
class Engine {
public:
void start() {
std::cout << "started" << std::endl;
}
int getSpeed() const {
return speed;
}
private:
int speed;
};
namespace utils {
int helper(int x) {
return x + 1;
}
}
struct Point {
double x;
double y;
};
"#;
let symbols = extract_symbols(source, Language::Cpp);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(names.contains(&"Engine"), "should find class Engine");
assert!(names.contains(&"Point"), "should find struct Point");
let engine = symbols.iter().find(|s| s.name == "Engine").unwrap();
assert_eq!(engine.kind, SymbolKind::Class);
}
#[test]
fn extract_cpp_qualified_method() {
let source = "void MyClass::process(int x) {\n // body\n}\n";
let symbols = extract_symbols(source, Language::Cpp);
let names: Vec<&str> = symbols.iter().map(|s| s.name.as_str()).collect();
assert!(
names.contains(&"process"),
"should find qualified method process, got: {names:?}"
);
}
#[test]
fn full_span_no_attributes() {
let source = "fn foo() {\n 42\n}\n";
let symbols = extract_symbols(source, Language::Rust);
let sym = &symbols[0];
let (start, end) = full_symbol_span(source, sym, Language::Rust);
assert_eq!(start, sym.start_line);
assert_eq!(end, sym.end_line);
}
#[test]
fn full_span_single_attribute() {
let source = "#[test]\nfn foo() {\n 42\n}\n";
let symbols = extract_symbols(source, Language::Rust);
let sym = &symbols[0];
assert_eq!(sym.start_line, 2); let (start, end) = full_symbol_span(source, sym, Language::Rust);
assert_eq!(start, 1); assert_eq!(end, sym.end_line);
}
#[test]
fn full_span_stacked_attributes() {
let source = "#[test]\n#[cfg(unix)]\nfn foo() {}\n";
let symbols = extract_symbols(source, Language::Rust);
let sym = &symbols[0];
let (start, _) = full_symbol_span(source, sym, Language::Rust);
assert_eq!(start, 1); }
#[test]
fn full_span_doc_comment() {
let source = "/// This is a doc comment.\n/// Second line.\nfn foo() {}\n";
let symbols = extract_symbols(source, Language::Rust);
let sym = &symbols[0];
let (start, _) = full_symbol_span(source, sym, Language::Rust);
assert_eq!(start, 1);
}
#[test]
fn full_span_mixed_attrs_and_docs() {
let source = "/// A doc comment.\n#[test]\n#[cfg(unix)]\nfn foo() {}\n";
let symbols = extract_symbols(source, Language::Rust);
let sym = &symbols[0];
let (start, _) = full_symbol_span(source, sym, Language::Rust);
assert_eq!(start, 1);
}
#[test]
fn full_span_python_decorator() {
let source = "@staticmethod\ndef foo():\n pass\n";
let symbols = extract_symbols(source, Language::Python);
let sym = &symbols[0];
let (start, _) = full_symbol_span(source, sym, Language::Python);
assert_eq!(start, 1);
}
#[test]
fn full_span_python_multiline_decorator() {
let source = "\
@decorator(
arg1,
arg2
)
def foo():
pass
";
let symbols = extract_symbols(source, Language::Python);
let sym = symbols.iter().find(|s| s.name == "foo").unwrap();
let (start, _) = full_symbol_span(source, sym, Language::Python);
assert_eq!(
start, 1,
"multiline @decorator(...) should be included in foo's span"
);
}
#[test]
fn full_span_python_stacked_multiline_decorator() {
let source = "\
@first_decorator
@second_decorator(
option=True
)
def bar():
pass
";
let symbols = extract_symbols(source, Language::Python);
let sym = symbols.iter().find(|s| s.name == "bar").unwrap();
let (start, _) = full_symbol_span(source, sym, Language::Python);
assert_eq!(
start, 1,
"both decorators (including multiline) should be included"
);
}
#[test]
fn full_span_excludes_inner_doc_comments() {
let source = "//! Module-level doc.\nstruct Config {}\n";
let symbols = extract_symbols(source, Language::Rust);
let sym = symbols.iter().find(|s| s.name == "Config").unwrap();
let (start, _) = full_symbol_span(source, sym, Language::Rust);
assert_eq!(
start, 2,
"inner doc comment //! should not be included in Config's span"
);
}
#[test]
fn full_span_stops_at_unrelated_code() {
let source = "fn bar() {}\n\n#[test]\nfn foo() {}\n";
let symbols = extract_symbols(source, Language::Rust);
let foo = symbols.iter().find(|s| s.name == "foo").unwrap();
let (start, _) = full_symbol_span(source, foo, Language::Rust);
assert_eq!(start, 3); }
#[test]
fn full_span_multiline_rust_attribute() {
let source = "\
#[cfg_attr(
feature = \"serde\",
derive(Serialize, Deserialize)
)]
struct Config {
name: String,
}
";
let symbols = extract_symbols(source, Language::Rust);
let sym = symbols.iter().find(|s| s.name == "Config").unwrap();
let (start, _) = full_symbol_span(source, sym, Language::Rust);
assert_eq!(
start, 1,
"multiline #[cfg_attr(...)] should be included in Config's span"
);
}
#[test]
fn full_span_multiline_attribute_with_stacked_single() {
let source = "\
#[derive(Debug)]
#[cfg_attr(
feature = \"serde\",
derive(Serialize)
)]
pub struct Foo {}
";
let symbols = extract_symbols(source, Language::Rust);
let sym = symbols.iter().find(|s| s.name == "Foo").unwrap();
let (start, _) = full_symbol_span(source, sym, Language::Rust);
assert_eq!(
start, 1,
"both #[derive(Debug)] and the multiline #[cfg_attr] should be included"
);
}
#[test]
fn extract_symbol_text_basic() {
let source = "#[test]\nfn foo() {\n 42\n}\n\nfn bar() {}\n";
let symbols = extract_symbols(source, Language::Rust);
let foo = symbols.iter().find(|s| s.name == "foo").unwrap();
let text = extract_symbol_text(source, foo, Language::Rust);
assert!(text.contains("#[test]"));
assert!(text.contains("fn foo()"));
assert!(!text.contains("fn bar"));
}
#[test]
fn extract_symbol_text_mixed_line_endings() {
let source = "fn first() {}\r\nfn second() {\n 42\n}\n";
let symbols = extract_symbols(source, Language::Rust);
let second = symbols.iter().find(|s| s.name == "second").unwrap();
let text = extract_symbol_text(source, second, Language::Rust);
assert!(
text.contains("fn second()"),
"should contain fn second(): {text:?}"
);
assert!(
!text.contains("fn first()"),
"should not contain fn first(): {text:?}"
);
}
#[test]
fn extract_symbol_text_crlf() {
let source = "fn first() {}\r\nfn second() {\r\n 42\r\n}\r\n";
let symbols = extract_symbols(source, Language::Rust);
let second = symbols.iter().find(|s| s.name == "second").unwrap();
let text = extract_symbol_text(source, second, Language::Rust);
assert!(
text.contains("fn second()"),
"extracted text should contain 'fn second()', got: {:?}",
text
);
assert!(
!text.contains("fn first()"),
"extracted text should not contain 'fn first()', got: {:?}",
text
);
}
#[test]
fn go_receiver_methods_grouped_under_struct() {
let source = "\
package main
type Server struct {
\tHost string
}
func NewServer() *Server { return nil }
func (s *Server) Start() error { return nil }
func (s *Server) Stop() {}
";
let syms = extract_symbols(source, Language::Go);
let server = syms.iter().find(|s| s.name == "Server").unwrap();
assert_eq!(server.kind, SymbolKind::Struct);
assert_eq!(
server.children.len(),
2,
"Server should have 2 method children, got: {:?}",
server.children.iter().map(|c| &c.name).collect::<Vec<_>>()
);
assert!(server.children.iter().any(|c| c.name == "Start"));
assert!(server.children.iter().any(|c| c.name == "Stop"));
assert!(
syms.iter()
.any(|s| s.name == "NewServer" && s.kind == SymbolKind::Function)
);
}
#[test]
fn go_qualified_name_lookup_works() {
let source = "\
package main
type Dog struct{}
func (d *Dog) Speak() string { return \"woof\" }
type Cat struct{}
func (c *Cat) Speak() string { return \"meow\" }
";
let syms = extract_symbols(source, Language::Go);
let dog_speak = find_symbol(&syms, "Dog::Speak").expect("Dog::Speak should be found");
assert!(dog_speak.signature.contains("Dog"));
let cat_speak = find_symbol(&syms, "Cat::Speak").expect("Cat::Speak should be found");
assert!(cat_speak.signature.contains("Cat"));
}
#[test]
fn go_value_receiver_grouped() {
let source = "\
package main
type Counter struct{ n int }
func (c Counter) Count() int { return c.n }
";
let syms = extract_symbols(source, Language::Go);
let counter = syms.iter().find(|s| s.name == "Counter").unwrap();
assert_eq!(counter.children.len(), 1);
assert_eq!(counter.children[0].name, "Count");
}
#[test]
fn go_method_without_matching_struct_stays_top_level() {
let source = "\
package main
func (e *External) DoWork() {}
";
let syms = extract_symbols(source, Language::Go);
assert!(
syms.iter()
.any(|s| s.name == "DoWork" && s.kind == SymbolKind::Method),
"orphan method should stay top-level"
);
}