@@ -95,6 +95,183 @@ fn make_file_change(path: &str, diff: &str, additions: usize, deletions: usize)
9595 }
9696}
9797
98+ /// Helper: parse source code with the given file extension and return extracted symbols.
99+ /// Creates a synthetic FileChange covering all lines, so all symbols are captured.
100+ #[ cfg( any(
101+ feature = "lang-rust" ,
102+ feature = "lang-typescript" ,
103+ feature = "lang-python" ,
104+ feature = "lang-go" ,
105+ feature = "lang-java" ,
106+ feature = "lang-c" ,
107+ feature = "lang-cpp" ,
108+ feature = "lang-ruby" ,
109+ feature = "lang-csharp"
110+ ) ) ]
111+ fn extract_symbols_from_source ( source : & str , ext : & str ) -> Vec < commitbee:: domain:: CodeSymbol > {
112+ let line_count = source. lines ( ) . count ( ) ;
113+ let path = format ! ( "src/test_file.{ext}" ) ;
114+ let diff = format ! ( "@@ -0,0 +1,{line_count} @@\n +placeholder\n " ) ;
115+ let change = make_file_change ( & path, & diff, line_count, 0 ) ;
116+ let staged_map = HashMap :: from ( [ ( PathBuf :: from ( & path) , source. to_string ( ) ) ] ) ;
117+ let head_map = HashMap :: new ( ) ;
118+ let analyzer = AnalyzerService :: new ( ) . expect ( "AnalyzerService::new() should succeed" ) ;
119+ analyzer. extract_symbols ( & [ change] , & staged_map, & head_map)
120+ }
121+
122+ // ─── Parent scope extraction ─────────────────────────────────────────────────
123+
124+ #[ cfg( feature = "lang-rust" ) ]
125+ mod rust_parent_scope {
126+ use super :: * ;
127+
128+ #[ test]
129+ fn rust_impl_method_has_parent_scope ( ) {
130+ let source = r#"impl CommitValidator {
131+ pub fn validate(&self, input: &str) -> bool {
132+ true
133+ }
134+ }"# ;
135+ let symbols = extract_symbols_from_source ( source, "rs" ) ;
136+ let method = symbols
137+ . iter ( )
138+ . find ( |s| s. name == "validate" )
139+ . expect ( "should find validate" ) ;
140+ assert_eq ! ( method. parent_scope. as_deref( ) , Some ( "CommitValidator" ) ) ;
141+ }
142+
143+ #[ test]
144+ fn rust_top_level_function_has_no_parent_scope ( ) {
145+ let source = "pub fn standalone() -> bool {\n true\n }\n " ;
146+ let symbols = extract_symbols_from_source ( source, "rs" ) ;
147+ let func = symbols
148+ . iter ( )
149+ . find ( |s| s. name == "standalone" )
150+ . expect ( "should find standalone" ) ;
151+ assert_eq ! ( func. parent_scope, None ) ;
152+ }
153+
154+ #[ test]
155+ fn rust_trait_method_has_parent_scope ( ) {
156+ let source = r#"trait Validator {
157+ fn validate(&self) -> bool;
158+ }"# ;
159+ let symbols = extract_symbols_from_source ( source, "rs" ) ;
160+ // Trait methods may or may not be captured depending on the .scm query
161+ if let Some ( method) = symbols. iter ( ) . find ( |s| s. name == "validate" ) {
162+ assert_eq ! ( method. parent_scope. as_deref( ) , Some ( "Validator" ) ) ;
163+ }
164+ }
165+ }
166+
167+ #[ cfg( feature = "lang-python" ) ]
168+ mod python_parent_scope {
169+ use super :: * ;
170+
171+ #[ test]
172+ fn python_class_method_has_parent_scope ( ) {
173+ let source = "class MyService:\n def process(self, data):\n return data\n " ;
174+ let symbols = extract_symbols_from_source ( source, "py" ) ;
175+ if let Some ( method) = symbols. iter ( ) . find ( |s| s. name == "process" ) {
176+ assert_eq ! ( method. parent_scope. as_deref( ) , Some ( "MyService" ) ) ;
177+ }
178+ }
179+
180+ #[ test]
181+ fn python_top_level_function_has_no_parent_scope ( ) {
182+ let source = "def standalone(x):\n return x\n " ;
183+ let symbols = extract_symbols_from_source ( source, "py" ) ;
184+ let func = symbols
185+ . iter ( )
186+ . find ( |s| s. name == "standalone" )
187+ . expect ( "should find standalone" ) ;
188+ assert_eq ! ( func. parent_scope, None ) ;
189+ }
190+ }
191+
192+ #[ cfg( feature = "lang-typescript" ) ]
193+ mod typescript_parent_scope {
194+ use super :: * ;
195+
196+ #[ test]
197+ fn typescript_class_method_has_parent_scope ( ) {
198+ let source = "class UserService {\n getName(): string {\n return 'test';\n }\n }\n " ;
199+ let symbols = extract_symbols_from_source ( source, "ts" ) ;
200+ if let Some ( method) = symbols. iter ( ) . find ( |s| s. name == "getName" ) {
201+ assert_eq ! ( method. parent_scope. as_deref( ) , Some ( "UserService" ) ) ;
202+ }
203+ }
204+
205+ #[ test]
206+ fn typescript_top_level_function_has_no_parent_scope ( ) {
207+ let source = "function standalone(): void {\n return;\n }\n " ;
208+ let symbols = extract_symbols_from_source ( source, "ts" ) ;
209+ let func = symbols
210+ . iter ( )
211+ . find ( |s| s. name == "standalone" )
212+ . expect ( "should find standalone" ) ;
213+ assert_eq ! ( func. parent_scope, None ) ;
214+ }
215+ }
216+
217+ #[ cfg( feature = "lang-java" ) ]
218+ mod java_parent_scope {
219+ use super :: * ;
220+
221+ #[ test]
222+ fn java_class_method_has_parent_scope ( ) {
223+ let source = "public class Calculator {\n public int add(int a, int b) {\n return a + b;\n }\n }\n " ;
224+ let symbols = extract_symbols_from_source ( source, "java" ) ;
225+ if let Some ( method) = symbols. iter ( ) . find ( |s| s. name == "add" ) {
226+ assert_eq ! ( method. parent_scope. as_deref( ) , Some ( "Calculator" ) ) ;
227+ }
228+ }
229+ }
230+
231+ #[ cfg( feature = "lang-go" ) ]
232+ mod go_parent_scope {
233+ use super :: * ;
234+
235+ #[ test]
236+ fn go_top_level_function_has_no_parent_scope ( ) {
237+ let source = "func ParseConfig(path string) error {\n \t return nil\n }\n " ;
238+ let symbols = extract_symbols_from_source ( source, "go" ) ;
239+ let func = symbols
240+ . iter ( )
241+ . find ( |s| s. name == "ParseConfig" )
242+ . expect ( "should find ParseConfig" ) ;
243+ assert_eq ! ( func. parent_scope, None ) ;
244+ }
245+ }
246+
247+ #[ cfg( feature = "lang-ruby" ) ]
248+ mod ruby_parent_scope {
249+ use super :: * ;
250+
251+ #[ test]
252+ fn ruby_class_method_has_parent_scope ( ) {
253+ let source = "class Calculator\n def add(a, b)\n a + b\n end\n end\n " ;
254+ let symbols = extract_symbols_from_source ( source, "rb" ) ;
255+ if let Some ( method) = symbols. iter ( ) . find ( |s| s. name == "add" ) {
256+ assert_eq ! ( method. parent_scope. as_deref( ) , Some ( "Calculator" ) ) ;
257+ }
258+ }
259+ }
260+
261+ #[ cfg( feature = "lang-csharp" ) ]
262+ mod csharp_parent_scope {
263+ use super :: * ;
264+
265+ #[ test]
266+ fn csharp_class_method_has_parent_scope ( ) {
267+ let source = "public class Calculator {\n public int Add(int a, int b) {\n return a + b;\n }\n }\n " ;
268+ let symbols = extract_symbols_from_source ( source, "cs" ) ;
269+ if let Some ( method) = symbols. iter ( ) . find ( |s| s. name == "Add" ) {
270+ assert_eq ! ( method. parent_scope. as_deref( ) , Some ( "Calculator" ) ) ;
271+ }
272+ }
273+ }
274+
98275// ─── Java ────────────────────────────────────────────────────────────────────
99276
100277#[ cfg( feature = "lang-java" ) ]
0 commit comments