#[test]
fn test_language_to_file_glob_rust() {
assert_eq!(super::language_to_file_glob("rust"), "**/*.rs");
}
#[test]
fn test_language_to_file_glob_typescript() {
assert_eq!(super::language_to_file_glob("typescript"), "**/*.{ts,tsx}");
}
#[test]
fn test_language_to_file_glob_javascript() {
assert_eq!(super::language_to_file_glob("javascript"), "**/*.{js,jsx}");
}
#[test]
fn test_language_to_file_glob_python() {
assert_eq!(super::language_to_file_glob("python"), "**/*.py");
}
#[test]
fn test_language_to_file_glob_go() {
assert_eq!(super::language_to_file_glob("go"), "**/*.go");
}
#[test]
fn test_language_to_file_glob_vue() {
assert_eq!(
super::language_to_file_glob("vue"),
"**/*.{vue,ts,tsx,js,jsx,mjs,cjs}"
);
}
#[test]
fn test_language_to_file_glob_java() {
assert_eq!(super::language_to_file_glob("java"), "**/*.java");
}
#[test]
fn test_language_to_file_glob_unknown_defaults_to_catch_all() {
assert_eq!(super::language_to_file_glob("haskell"), "**/*");
assert_eq!(super::language_to_file_glob(""), "**/*");
}
#[test]
fn test_definition_patterns_rust_fn() {
let patterns = super::definition_patterns("rs", "my_function");
assert!(!patterns.is_empty(), "must return at least one pattern");
let re = regex::Regex::new(&patterns[0]).expect("valid regex");
assert!(
re.is_match("pub async fn my_function("),
"must match 'pub async fn my_function('"
);
assert!(
re.is_match("fn my_function("),
"must match bare 'fn my_function('"
);
assert!(
!re.is_match("let my_function = 42"),
"must not match variable assignment"
);
}
#[test]
fn test_definition_patterns_rust_struct() {
let patterns = super::definition_patterns("rs", "MyStruct");
assert!(patterns.len() >= 2, "must return patterns for types too");
let re = regex::Regex::new(&patterns[1]).expect("valid regex");
assert!(
re.is_match("pub(crate) struct MyStruct {"),
"must match 'pub(crate) struct MyStruct {{'"
);
assert!(
re.is_match("enum MyStruct {"),
"must match 'enum MyStruct {{'"
);
}
#[test]
fn test_definition_patterns_ts_export_class() {
let patterns = super::definition_patterns("ts", "AuthService");
assert!(!patterns.is_empty());
let re = regex::Regex::new(&patterns[1]).expect("valid regex");
assert!(
re.is_match("export default class AuthService {"),
"must match 'export default class AuthService {{'"
);
assert!(
re.is_match("export interface AuthService {"),
"must match 'export interface AuthService {{'"
);
}
#[test]
fn test_definition_patterns_vue_function() {
let patterns = super::definition_patterns("vue", "handleClick");
assert!(!patterns.is_empty(), "vue must have definition patterns");
let re = regex::Regex::new(&patterns[0]).expect("valid regex");
assert!(
re.is_match("export async function handleClick("),
"must match 'export async function handleClick('"
);
assert!(
re.is_match("function handleClick("),
"must match bare 'function handleClick('"
);
}
#[test]
fn test_definition_patterns_vue_const_assignment() {
let patterns = super::definition_patterns("vue", "handleClick");
assert!(patterns.len() >= 3);
let re = regex::Regex::new(&patterns[2]).expect("valid regex");
assert!(
re.is_match("const handleClick = () => {}"),
"must match 'const handleClick = () => {{}}'"
);
assert!(
re.is_match("export const handleClick = () => {}"),
"must match 'export const handleClick = () => {{}}'"
);
assert!(
re.is_match("let handleClick: Handler = () => {}"),
"must match typed assignment 'let handleClick: Handler ='"
);
}
#[test]
fn test_definition_patterns_vue_ref() {
let patterns = super::definition_patterns("vue", "count");
assert!(patterns.len() >= 3);
let re = regex::Regex::new(&patterns[2]).expect("valid regex");
assert!(
re.is_match("const count = ref(0)"),
"must match 'const count = ref(0)'"
);
assert!(
re.is_match("const count = reactive({ value: 0 })"),
"must match 'const count = reactive(...)'"
);
assert!(
re.is_match("const count = computed(() => 0)"),
"must match 'const count = computed(...)'"
);
}
#[test]
fn test_definition_patterns_vue_define_macros() {
let patterns_props = super::definition_patterns("vue", "props");
let patterns_emit = super::definition_patterns("vue", "emit");
assert!(patterns_props.len() >= 5);
let re_props = regex::Regex::new(&patterns_props[4]).expect("valid regex");
let re_emit = regex::Regex::new(&patterns_emit[4]).expect("valid regex");
assert!(
re_props.is_match("const props = defineProps<{ id: string }>()"),
"must match 'const props = defineProps(...)'"
);
assert!(
re_emit.is_match("const emit = defineEmits<{ (e: 'save'): void }>()"),
"must match 'const emit = defineEmits(...)'"
);
assert!(
re_props.is_match("const props = withDefaults(defineProps<{ }>(), {})"),
"must match 'const props = withDefaults(...)'"
);
}
#[test]
fn test_definition_patterns_py_async_def() {
let patterns = super::definition_patterns("py", "process_order");
assert!(!patterns.is_empty());
let re = regex::Regex::new(&patterns[0]).expect("valid regex");
assert!(
re.is_match("async def process_order("),
"must match 'async def process_order('"
);
assert!(
re.is_match("def process_order("),
"must match 'def process_order('"
);
}
#[test]
fn test_definition_patterns_py_class() {
let patterns = super::definition_patterns("py", "MyClass");
assert!(patterns.len() >= 2);
let re = regex::Regex::new(&patterns[1]).expect("valid regex");
assert!(re.is_match("class MyClass:"), "must match 'class MyClass:'");
}
#[test]
fn test_definition_patterns_go_receiver_method() {
let patterns = super::definition_patterns("go", "HandleRequest");
assert!(!patterns.is_empty());
let re = regex::Regex::new(&patterns[0]).expect("valid regex");
assert!(
re.is_match("func (s *Service) HandleRequest("),
"must match receiver method"
);
assert!(
re.is_match("func HandleRequest("),
"must match bare function"
);
}
#[test]
fn test_definition_patterns_go_type() {
let patterns = super::definition_patterns("go", "UserService");
assert!(patterns.len() >= 3, "go must have func + type + const/var");
let re = regex::Regex::new(&patterns[2]).expect("valid regex");
assert!(
re.is_match("type UserService struct {"),
"must match 'type UserService struct {{'"
);
}
#[test]
fn test_definition_patterns_unknown_extension_uses_fallback() {
let patterns = super::definition_patterns("java", "MyClass");
assert!(!patterns.is_empty());
let re = regex::Regex::new(&patterns[0]).expect("valid regex");
assert!(
re.is_match("public class MyClass {"),
"must match Java class declaration"
);
}
#[test]
fn test_definition_patterns_catch_all_extension() {
let patterns = super::definition_patterns("unknown_ext", "foo");
assert_eq!(
patterns.len(),
1,
"catch-all must return exactly one pattern"
);
let re = regex::Regex::new(&patterns[0]).expect("valid regex");
assert!(re.is_match("foo"), "must match bare word");
assert!(!re.is_match("foobar"), "must not match partial word");
}
#[test]
fn test_definition_patterns_special_chars_escaped() {
let patterns = super::definition_patterns("rs", "my+function");
assert!(!patterns.is_empty());
let re = regex::Regex::new(&patterns[0]).expect("valid regex");
assert!(re.is_match("fn my+function("));
assert!(!re.is_match("fn myXfunction("));
}
#[test]
fn test_definition_patterns_all_languages_compile() {
let extensions = [
"rs", "ts", "tsx", "js", "jsx", "py", "go", "java", "vue", "xyz",
];
for ext in &extensions {
let patterns = super::definition_patterns(ext, "TestSymbol");
for (i, pattern) in patterns.iter().enumerate() {
assert!(
regex::Regex::new(pattern).is_ok(),
"pattern {i} for ext '{ext}' must be valid regex: {pattern}"
);
}
}
}
#[test]
fn test_definition_patterns_java_class() {
let patterns = super::definition_patterns("java", "MyClass");
assert!(!patterns.is_empty(), "java must have definition patterns");
let re = regex::Regex::new(&patterns[0]).expect("valid regex");
assert!(
re.is_match("public class MyClass {"),
"must match 'public class MyClass {{'"
);
assert!(
re.is_match("private static final class MyClass {"),
"must match 'private static final class MyClass {{'"
);
}
#[test]
fn test_definition_patterns_java_constructor() {
let patterns = super::definition_patterns("java", "MyClass");
assert!(!patterns.is_empty(), "java must have definition patterns");
let constructor_pattern = patterns.iter().find(|p| p.contains("MyClass\\s*\\("));
assert!(
constructor_pattern.is_some(),
"java must have a constructor pattern"
);
let re = regex::Regex::new(constructor_pattern.unwrap()).expect("valid regex");
assert!(
re.is_match("public MyClass(String name) {"),
"must match 'public MyClass(String name) {{'"
);
assert!(
re.is_match("MyClass(String name, int age) {"),
"must match bare 'MyClass(String name, int age) {{'"
);
assert!(
re.is_match("private MyClass() {"),
"must match 'private MyClass() {{'"
);
}
#[test]
fn test_definition_patterns_java_record() {
let patterns = super::definition_patterns("java", "Person");
assert!(!patterns.is_empty(), "java must have definition patterns");
let record_pattern = patterns.iter().find(|p| p.contains("record"));
assert!(record_pattern.is_some(), "java must have a record pattern");
let re = regex::Regex::new(record_pattern.unwrap()).expect("valid regex");
assert!(
re.is_match("public record Person(String name) {"),
"must match 'public record Person(String name) {{'"
);
assert!(
re.is_match("record Person(String name, int age) {"),
"must match bare 'record Person(String name, int age) {{'"
);
assert!(
re.is_match("private final record Person(String name) {"),
"must match 'private final record Person(String name) {{'"
);
}
#[test]
fn test_definition_patterns_java_sealed_class() {
let patterns = super::definition_patterns("java", "Shape");
assert!(!patterns.is_empty(), "java must have definition patterns");
let sealed_pattern = patterns.iter().find(|p| p.contains("sealed"));
assert!(
sealed_pattern.is_some(),
"java must have a sealed class/interface pattern"
);
let re = regex::Regex::new(sealed_pattern.unwrap()).expect("valid regex");
assert!(
re.is_match("public sealed class Shape permits Circle, Square {"),
"must match 'public sealed class Shape permits Circle, Square {{'"
);
assert!(
re.is_match("sealed interface Shape permits Circle {"),
"must match 'sealed interface Shape permits Circle {{'"
);
assert!(
re.is_match("private sealed abstract class Shape {"),
"must match 'private sealed abstract class Shape {{'"
);
}
#[test]
fn test_definition_patterns_java_annotated_method() {
let patterns = super::definition_patterns("java", "myService");
let method_pattern = patterns.last().expect("java should have patterns");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
re.is_match("@Bean public MyService myService()"),
"must match '@Bean public MyService myService()'"
);
assert!(
re.is_match("@Override public void myService()"),
"must match '@Override public void myService()'"
);
assert!(
re.is_match("@GetMapping public Response myService()"),
"must match '@GetMapping public Response myService()'"
);
}
#[test]
fn test_definition_patterns_java_primitive_return() {
let patterns = super::definition_patterns("java", "process");
assert!(!patterns.is_empty(), "java must have definition patterns");
let method_pattern = patterns.last().expect("java should have patterns");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(re.is_match("void process()"), "must match 'void process()'");
assert!(
re.is_match("public boolean process()"),
"must match 'public boolean process()'"
);
assert!(
re.is_match("private int process()"),
"must match 'private int process()'"
);
assert!(
re.is_match("protected String process()"),
"must match 'protected String process()'"
);
assert!(
re.is_match("static final double process()"),
"must match 'static final double process()'"
);
}
#[test]
fn test_definition_patterns_java_generic_return() {
let patterns = super::definition_patterns("java", "process");
assert!(!patterns.is_empty(), "java must have definition patterns");
let method_pattern = patterns.last().expect("java should have patterns");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
re.is_match("public List<String> process()"),
"must match 'public List<String> process()'"
);
assert!(
re.is_match("Map<String, Integer> process()"),
"must match 'Map<String, Integer> process()'"
);
}
#[test]
fn test_definition_patterns_java_array_return() {
let patterns = super::definition_patterns("java", "process");
assert!(!patterns.is_empty(), "java must have definition patterns");
let method_pattern = patterns.last().expect("java should have patterns");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
re.is_match("public String[] process()"),
"must match 'public String[] process()'"
);
assert!(
re.is_match("int[] process()"),
"must match 'int[] process()'"
);
assert!(
re.is_match("public int[][] process()"),
"must match 'public int[][] process()' — multi-dimensional array"
);
assert!(
re.is_match("String[][][] process()"),
"must match 'String[][][] process()' — 3D array"
);
}
#[test]
fn test_definition_patterns_java_method_with_type_params() {
let patterns = super::definition_patterns("java", "process");
assert!(!patterns.is_empty(), "java must have definition patterns");
let method_pattern = patterns.last().expect("java should have patterns");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
re.is_match("public <T> T process()"),
"must match 'public <T> T process()'"
);
assert!(
re.is_match("<T, U> Map<T, U> process()"),
"must match '<T, U> Map<T, U> process()'"
);
}
#[test]
fn test_definition_patterns_java_constructor_rejects_return_types() {
let patterns = super::definition_patterns("java", "MyClass");
let constructor_pattern = patterns
.get(1)
.expect("java should have constructor pattern");
let re = regex::Regex::new(constructor_pattern).expect("valid regex");
assert!(
!re.is_match("public void MyClass()"),
"must NOT match 'public void MyClass()' - this is a method, not a constructor"
);
assert!(
!re.is_match("private String MyClass()"),
"must NOT match 'private String MyClass()' - this is a method, not a constructor"
);
assert!(
!re.is_match("protected int MyClass()"),
"must NOT match 'protected int MyClass()' - this is a method, not a constructor"
);
}
#[test]
fn test_definition_patterns_java_method_pattern_rejects_new_and_throw() {
let patterns = super::definition_patterns("java", "MyError");
let method_pattern = patterns.last().expect("java should have method pattern");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
!re.is_match("throw new MyError(msg)"),
"must NOT match 'throw new MyError(msg)' - false positive"
);
assert!(
!re.is_match("return new MyError()"),
"must NOT match 'return new MyError()' - false positive"
);
assert!(
!re.is_match("new MyError().getMessage()"),
"must NOT match 'new MyError().getMessage()' - false positive"
);
}
#[test]
fn test_definition_patterns_java_constructor_rejects_new_keyword() {
let patterns = super::definition_patterns("java", "MyClass");
let constructor_pattern = patterns
.get(1)
.expect("java should have constructor pattern");
let re = regex::Regex::new(constructor_pattern).expect("valid regex");
assert!(
!re.is_match("new MyClass()"),
"must NOT match 'new MyClass()' - this is a call, not a definition"
);
assert!(
!re.is_match("return new MyClass()"),
"must NOT match 'return new MyClass()' - this is a call, not a definition"
);
}
#[test]
fn test_definition_patterns_java_generic_constructor() {
let patterns = super::definition_patterns("java", "MyClass");
let constructor_pattern = patterns
.get(1)
.expect("java should have constructor pattern");
let re = regex::Regex::new(constructor_pattern).expect("valid regex");
assert!(
re.is_match("public <E> MyClass(E item)"),
"must match 'public <E> MyClass(E item)'"
);
assert!(
re.is_match("<T, U> MyClass(T a, U b)"),
"must match '<T, U> MyClass(T a, U b)'"
);
}
#[test]
fn test_definition_patterns_java_nested_generics() {
let patterns = super::definition_patterns("java", "process");
let method_pattern = patterns.last().expect("java should have method pattern");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
re.is_match("public Map<String, List<Integer>> process()"),
"must match 'public Map<String, List<Integer>> process()'"
);
assert!(
re.is_match("Map<String, Map<String, Integer>> process()"),
"must match 'Map<String, Map<String, Integer>> process()'"
);
}
#[test]
fn test_definition_patterns_java_sealed_no_trailing_whitespace() {
let patterns = super::definition_patterns("java", "Shape");
let class_pattern = patterns.first().expect("java should have class pattern");
let re = regex::Regex::new(class_pattern).expect("valid regex");
assert!(
re.is_match("public sealed class Shape"),
"must match 'public sealed class Shape' at end-of-line"
);
assert!(
re.is_match("sealed class Shape{"),
"must match 'sealed class Shape{{' without space before brace"
);
}
#[test]
fn test_definition_patterns_java_strictfp_method() {
let patterns = super::definition_patterns("java", "calculate");
let method_pattern = patterns.last().expect("java should have method pattern");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
re.is_match("public strictfp void calculate()"),
"must match 'public strictfp void calculate()'"
);
assert!(
re.is_match("strictfp double calculate(int x)"),
"must match 'strictfp double calculate(int x)'"
);
}
#[test]
fn test_definition_patterns_java_strictfp_class() {
let patterns = super::definition_patterns("java", "MathUtils");
let class_pattern = patterns.first().expect("java should have class pattern");
let re = regex::Regex::new(class_pattern).expect("valid regex");
assert!(
re.is_match("strictfp class MathUtils"),
"must match 'strictfp class MathUtils'"
);
assert!(
re.is_match("public strictfp class MathUtils"),
"must match 'public strictfp class MathUtils'"
);
}
#[test]
fn test_definition_patterns_java_non_sealed_class() {
let patterns = super::definition_patterns("java", "Shape");
let class_pattern = patterns.first().expect("java should have class pattern");
let re = regex::Regex::new(class_pattern).expect("valid regex");
assert!(
re.is_match("non-sealed class Shape"),
"must match 'non-sealed class Shape'"
);
assert!(
re.is_match("public non-sealed class Shape"),
"must match 'public non-sealed class Shape'"
);
}
#[test]
fn test_definition_patterns_java_multi_dimensional_array_return() {
let patterns = super::definition_patterns("java", "getData");
let method_pattern = patterns.last().expect("java should have method pattern");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
re.is_match("public int[][] getData()"),
"must match 'public int[][] getData()' — 2D array"
);
assert!(
re.is_match("String[][][] getData()"),
"must match 'String[][][] getData()' — 3D array"
);
assert!(
re.is_match("Map<String, Integer>[][] getData()"),
"must match 'Map<String, Integer>[][] getData()' — generic 2D array"
);
}
#[test]
fn test_definition_patterns_java_bounded_generics() {
let patterns = super::definition_patterns("java", "sort");
let method_pattern = patterns.last().expect("java should have method pattern");
let re = regex::Regex::new(method_pattern).expect("valid regex");
assert!(
re.is_match("public <T extends Comparable<T>> void sort(List<T> list)"),
"must match 'public <T extends Comparable<T>> void sort(List<T> list)' — bounded generics"
);
let patterns_get = super::definition_patterns("java", "get");
let method_pattern_get = patterns_get
.last()
.expect("java should have method pattern");
let re_get = regex::Regex::new(method_pattern_get).expect("valid regex");
assert!(
re_get.is_match("<K, V extends Serializable> V get(K key)"),
"must match '<K, V extends Serializable> V get(K key)' — multiple bounded params"
);
let patterns2 = super::definition_patterns("java", "MyClass");
let constructor_pattern = patterns2
.get(1)
.expect("java should have constructor pattern");
let re2 = regex::Regex::new(constructor_pattern).expect("valid regex");
assert!(
re2.is_match("public <T extends Comparable<T>> MyClass(T item)"),
"must match 'public <T extends Comparable<T>> MyClass(T item)' — generic constructor with bounds"
);
}
#[test]
fn test_definition_patterns_java_static_record() {
let patterns = super::definition_patterns("java", "Inner");
let record_pattern = patterns.get(2).expect("java should have record pattern");
let re = regex::Regex::new(record_pattern).expect("valid regex");
assert!(
re.is_match("static record Inner(String name, int value)"),
"must match 'static record Inner(String name, int value)' — nested static record"
);
assert!(
re.is_match("public static final record Inner(String name)"),
"must match 'public static final record Inner(String name)' — full modifiers"
);
}
#[test]
fn test_extract_call_candidates_rust_basic() {
let code = r"
fn process() {
fetch_user(id);
validate_order(&order);
charge_payment(amount);
}
";
let candidates = super::extract_call_candidates(code, "rust");
assert!(candidates.contains(&"fetch_user".to_string()));
assert!(candidates.contains(&"validate_order".to_string()));
assert!(candidates.contains(&"charge_payment".to_string()));
}
#[test]
fn test_extract_call_candidates_filters_keywords() {
let code = r"
fn process() {
if condition { return; }
for item in items { do_something(item); }
while running { check(); }
match value { _ => {} }
}
";
let candidates = super::extract_call_candidates(code, "rust");
assert!(
!candidates.contains(&"if".to_string()),
"must filter 'if' keyword"
);
assert!(
!candidates.contains(&"for".to_string()),
"must filter 'for' keyword"
);
assert!(
!candidates.contains(&"while".to_string()),
"must filter 'while' keyword"
);
assert!(
!candidates.contains(&"match".to_string()),
"must filter 'match' keyword"
);
assert!(
!candidates.contains(&"return".to_string()),
"must filter 'return' keyword"
);
assert!(
candidates.contains(&"do_something".to_string()),
"must keep real function call"
);
assert!(
candidates.contains(&"check".to_string()),
"must keep real function call"
);
}
#[test]
fn test_extract_call_candidates_go_keywords() {
let code = r"
func process() {
if err != nil { return err }
for _, v := range items { handle(v) }
select { case <-ch: }
}
";
let candidates = super::extract_call_candidates(code, "go");
assert!(!candidates.contains(&"if".to_string()));
assert!(!candidates.contains(&"for".to_string()));
assert!(!candidates.contains(&"range".to_string()));
assert!(!candidates.contains(&"select".to_string()));
assert!(candidates.contains(&"handle".to_string()));
}
#[test]
fn test_extract_call_candidates_python_keywords() {
let code = r#"
def process():
if condition:
return result
for item in items:
handle(item)
raise ValueError("bad")
"#;
let candidates = super::extract_call_candidates(code, "python");
assert!(!candidates.contains(&"if".to_string()));
assert!(!candidates.contains(&"for".to_string()));
assert!(!candidates.contains(&"return".to_string()));
assert!(!candidates.contains(&"raise".to_string()));
assert!(candidates.contains(&"handle".to_string()));
}
#[test]
fn test_extract_call_candidates_deduplicates() {
let code = r"
fn process() {
fetch(id);
fetch(id);
fetch(id);
}
";
let candidates = super::extract_call_candidates(code, "rust");
let count = candidates.iter().filter(|c| *c == "fetch").count();
assert_eq!(count, 1, "must deduplicate call candidates");
}
#[test]
#[allow(clippy::format_push_string)]
fn test_extract_call_candidates_caps_at_20() {
let mut code = String::from("fn process() {\n");
for i in 0..25 {
code.push_str(&format!(" func_{i}(x);\n"));
}
code.push('}');
let candidates = super::extract_call_candidates(&code, "rust");
assert!(
candidates.len() <= 20,
"must cap at 20 candidates, got {}",
candidates.len()
);
}
#[test]
fn test_extract_call_candidates_typescript_method_calls() {
let code = r"
function process() {
user.getName();
order.calculateTotal();
service.validate(data);
}
";
let candidates = super::extract_call_candidates(code, "typescript");
assert!(candidates.contains(&"getName".to_string()));
assert!(candidates.contains(&"calculateTotal".to_string()));
assert!(candidates.contains(&"validate".to_string()));
}
#[test]
fn test_extract_call_candidates_vue_method_calls() {
let code = r"
const handleSubmit = () => {
userService.login(credentials);
router.push('/dashboard');
toast.showSuccess();
}
";
let candidates = super::extract_call_candidates(code, "vue");
assert!(
candidates.contains(&"login".to_string()),
"expected 'login' in {candidates:?}"
);
assert!(
candidates.contains(&"push".to_string()),
"expected 'push' in {candidates:?}"
);
assert!(
candidates.contains(&"showSuccess".to_string()),
"expected 'showSuccess' in {candidates:?}"
);
}
#[test]
fn test_extract_call_candidates_empty_input() {
let candidates = super::extract_call_candidates("", "rust");
assert!(candidates.is_empty(), "empty input must return empty vec");
}
#[test]
fn test_extract_call_candidates_no_calls() {
let code = "let x = 42;\nlet y = x + 1;";
let candidates = super::extract_call_candidates(code, "rust");
assert!(
candidates.is_empty(),
"no function calls must return empty vec"
);
}
#[test]
fn test_keywords_for_language_rust() {
let kw = super::keywords_for_language("rust");
assert!(kw.contains(&"fn"), "must contain 'fn'");
assert!(kw.contains(&"struct"), "must contain 'struct'");
assert!(kw.contains(&"impl"), "must contain 'impl'");
assert!(kw.contains(&"async"), "must contain 'async'");
assert!(kw.contains(&"await"), "must contain 'await'");
assert!(kw.len() > 20, "rust keywords must be comprehensive");
}
#[test]
fn test_keywords_for_language_go() {
let kw = super::keywords_for_language("go");
assert!(kw.contains(&"func"), "must contain 'func'");
assert!(kw.contains(&"defer"), "must contain 'defer'");
assert!(kw.contains(&"select"), "must contain 'select'");
assert!(kw.contains(&"chan"), "must contain 'chan'");
}
#[test]
fn test_keywords_for_language_typescript() {
let kw = super::keywords_for_language("typescript");
assert!(kw.contains(&"function"), "must contain 'function'");
assert!(kw.contains(&"class"), "must contain 'class'");
assert!(kw.contains(&"const"), "must contain 'const'");
}
#[test]
fn test_keywords_for_language_python() {
let kw = super::keywords_for_language("python");
assert!(kw.contains(&"def"), "must contain 'def'");
assert!(kw.contains(&"class"), "must contain 'class'");
assert!(kw.contains(&"raise"), "must contain 'raise'");
}
#[test]
fn test_keywords_for_language_java() {
let kw = super::keywords_for_language("java");
assert!(kw.contains(&"class"), "must contain 'class'");
assert!(kw.contains(&"interface"), "must contain 'interface'");
assert!(kw.contains(&"extends"), "must contain 'extends'");
}
#[test]
fn test_keywords_for_language_vue() {
let kw = super::keywords_for_language("vue");
assert!(kw.contains(&"function"), "must contain 'function'");
assert!(kw.contains(&"const"), "must contain 'const'");
assert!(kw.contains(&"ref"), "must contain 'ref'");
assert!(kw.contains(&"reactive"), "must contain 'reactive'");
assert!(kw.contains(&"computed"), "must contain 'computed'");
assert!(kw.contains(&"watch"), "must contain 'watch'");
assert!(kw.contains(&"onMounted"), "must contain 'onMounted'");
assert!(kw.contains(&"defineProps"), "must contain 'defineProps'");
assert!(kw.contains(&"defineEmits"), "must contain 'defineEmits'");
}
#[test]
fn test_keywords_for_language_unknown_uses_default() {
let kw = super::keywords_for_language("haskell");
assert!(kw.contains(&"if"), "default must contain 'if'");
assert!(kw.contains(&"for"), "default must contain 'for'");
assert!(kw.contains(&"while"), "default must contain 'while'");
assert!(kw.contains(&"return"), "default must contain 'return'");
}
#[test]
fn test_try_separator_correction_converts_double_colon_to_dot() {
assert_eq!(
super::PathfinderServer::try_separator_correction("cache.rs::AstCache::get_or_parse"),
Some("cache.rs::AstCache.get_or_parse".to_string())
);
assert_eq!(
super::PathfinderServer::try_separator_correction("file.rs::Struct::method::inner"),
Some("file.rs::Struct.method.inner".to_string())
);
assert_eq!(
super::PathfinderServer::try_separator_correction("file.rs::simple_symbol"),
None
);
assert_eq!(
super::PathfinderServer::try_separator_correction("file.rs"),
None
);
}
#[test]
fn test_is_workspace_file_heuristics() {
assert!(!super::is_workspace_file("/usr/bin/src/main.rs"));
assert!(!super::is_workspace_file("C:\\projects\\main.rs"));
assert!(!super::is_workspace_file("D:/projects/main.rs"));
assert!(!super::is_workspace_file("\\network\\main.rs"));
assert!(!super::is_workspace_file("node_modules/lodash/index.js"));
assert!(!super::is_workspace_file("node_modules\\lodash\\index.js"));
assert!(!super::is_workspace_file(
"vendor/github.com/pkg/errors/errors.go"
));
assert!(!super::is_workspace_file(
"vendor\\github.com\\pkg\\errors\\errors.go"
));
assert!(!super::is_workspace_file("std/src/lib.rs"));
assert!(!super::is_workspace_file("core/src/lib.rs"));
assert!(!super::is_workspace_file("alloc/src/lib.rs"));
assert!(!super::is_workspace_file("std"));
assert!(!super::is_workspace_file("core"));
assert!(!super::is_workspace_file("alloc"));
assert!(!super::is_workspace_file("library/std/src/path.rs"));
assert!(!super::is_workspace_file("library/core/src/lib.rs"));
assert!(!super::is_workspace_file("library/alloc/src/lib.rs"));
assert!(!super::is_workspace_file("library\\std\\src\\path.rs"));
assert!(!super::is_workspace_file("library\\core\\src\\lib.rs"));
assert!(!super::is_workspace_file("library\\alloc\\src\\lib.rs"));
assert!(super::is_workspace_file("src/main.rs"));
assert!(super::is_workspace_file("lib/utils.ts"));
assert!(!super::is_workspace_file("README.md"));
assert!(!super::is_workspace_file("package.json"));
}
#[tokio::test]
async fn test_enrich_did_you_mean_all_cases() {
use super::test_helpers::{make_server_with_lawyer, make_temp_workspace};
use pathfinder_common::config::PathfinderConfig;
use pathfinder_common::sandbox::Sandbox;
use pathfinder_common::types::WorkspaceRoot;
use pathfinder_treesitter::mock::MockSurgeon;
let mock_surgeon = std::sync::Arc::new(MockSurgeon::new());
let mock_lawyer = std::sync::Arc::new(pathfinder_lsp::MockLawyer::default());
let (server, _temp_dir) = make_server_with_lawyer(mock_surgeon.clone(), mock_lawyer.clone());
let original_suggestions = vec!["src/auth.rs::AuthService".to_string()];
let enriched = server
.enrich_did_you_mean("src/auth.rs::AuthService::login", original_suggestions)
.await;
assert_eq!(enriched.len(), 2);
assert_eq!(enriched[0], "src/auth.rs::AuthService.login");
assert_eq!(enriched[1], "src/auth.rs::AuthService");
let original_suggestions = vec![
"src/auth.rs::AuthService.login".to_string(),
"src/auth.rs::AuthService".to_string(),
];
let enriched = server
.enrich_did_you_mean("src/auth.rs::AuthService::login", original_suggestions)
.await;
assert_eq!(enriched.len(), 2);
assert_eq!(enriched[0], "src/auth.rs::AuthService.login");
assert_eq!(enriched[1], "src/auth.rs::AuthService");
let mock_scout = std::sync::Arc::new(pathfinder_search::MockScout::default());
mock_scout.set_result(Ok(pathfinder_search::SearchResult {
matches: vec![pathfinder_search::SearchMatch {
file: "src/auth.rs".to_owned(),
line: 1,
column: 1,
content: "fn login() {}".to_owned(),
context_before: vec![],
context_after: vec![],
enclosing_semantic_path: None,
version_hash: "hash".to_owned(),
is_definition: None,
known: None,
}],
total_matches: 1,
truncated: false,
files_searched: 1,
files_in_scope: 1,
binary_skipped: 0,
gitignored_skipped: 0,
other_skipped: 0,
}));
for _ in 0..100 {
mock_surgeon
.enclosing_symbol_detail_results
.lock()
.unwrap()
.push(Ok(None));
}
let ws_dir = make_temp_workspace();
let ws = WorkspaceRoot::new(ws_dir.path()).expect("valid root");
let config = PathfinderConfig::default();
let sandbox = Sandbox::new(ws.path(), &config.sandbox);
let server_with_scout = super::PathfinderServer::with_all_engines(
ws,
config,
sandbox,
mock_scout,
mock_surgeon.clone(),
mock_lawyer.clone(),
);
let enriched = server_with_scout
.enrich_did_you_mean("src/auth.rs::login", vec![])
.await;
assert!(enriched.contains(&"src/auth.rs::login".to_string()));
let enriched_err = server_with_scout
.enrich_did_you_mean("src/auth.rs::login/error", vec![])
.await;
assert!(enriched_err.is_empty());
}
#[tokio::test]
async fn test_read_symbol_scope_enriched_all_cases() {
use super::test_helpers::{make_scope, make_server_with_lawyer};
use pathfinder_treesitter::mock::MockSurgeon;
let mock_surgeon = std::sync::Arc::new(MockSurgeon::new());
let mock_lawyer = std::sync::Arc::new(pathfinder_lsp::MockLawyer::default());
let (server, _temp_dir) = make_server_with_lawyer(mock_surgeon.clone(), mock_lawyer.clone());
let scope = make_scope();
mock_surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(scope.clone()));
let semantic_path =
pathfinder_common::types::SemanticPath::parse("src/auth.rs::login").unwrap();
let res = server
.read_symbol_scope_enriched(&semantic_path, "src/auth.rs::login")
.await;
assert!(res.is_ok());
assert_eq!(res.unwrap().content, scope.content);
mock_surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Err(pathfinder_treesitter::SurgeonError::SymbolNotFound {
path: "src/auth.rs::login".to_owned(),
did_you_mean: vec![],
}));
let res = server
.read_symbol_scope_enriched(&semantic_path, "src/auth.rs::login")
.await;
assert!(res.is_err());
let err = res.unwrap_err();
let data = err.data.as_ref().expect("error should contain JSON data");
assert_eq!(data["error"], "SYMBOL_NOT_FOUND");
let corrected_scope = make_scope();
mock_surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Err(pathfinder_treesitter::SurgeonError::SymbolNotFound {
path: "src/auth.rs::AuthService::login".to_owned(),
did_you_mean: vec![],
}));
mock_surgeon
.read_symbol_scope_results
.lock()
.unwrap()
.push(Ok(corrected_scope.clone()));
let semantic_path_with_confusion =
pathfinder_common::types::SemanticPath::parse("src/auth.rs::AuthService::login").unwrap();
let res = server
.read_symbol_scope_enriched(
&semantic_path_with_confusion,
"src/auth.rs::AuthService::login",
)
.await;
assert!(res.is_ok());
assert_eq!(res.unwrap().content, corrected_scope.content);
}