@@ -93,10 +93,18 @@ fn match_js_type_map(node: &Node, source: &[u8], symbols: &mut FileSymbols, _dep
9393 } ) ;
9494 }
9595 }
96+ // Phase 8.3e: Object.create({ key: fn }) → composite pts key per property
97+ if value_n. kind ( ) == "call_expression" {
98+ seed_object_create_entries ( var_name, & value_n, source, symbols) ;
99+ }
96100 }
97101 }
98102 }
99103 }
104+ // Phase 8.3e: Object.defineProperty / defineProperties → composite pts key
105+ "call_expression" => {
106+ seed_define_property_entries ( node, source, symbols) ;
107+ }
100108 "required_parameter" | "optional_parameter" => {
101109 let name_node = node. child_by_field_name ( "pattern" )
102110 . or_else ( || node. child_by_field_name ( "left" ) )
@@ -194,13 +202,158 @@ fn is_js_builtin_global(name: &str) -> bool {
194202 )
195203}
196204
205+ // ── Phase 8.3e: Object.defineProperty / defineProperties / create ────────────
206+
207+ /// Seed composite pts keys for `Object.defineProperty(obj, "key", { value: fn })`
208+ /// and `Object.defineProperties(obj, { "key": { value: fn }, ... })`.
209+ fn seed_define_property_entries ( node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
210+ let Some ( callee) = node. child_by_field_name ( "function" ) else { return } ;
211+ if callee. kind ( ) != "member_expression" { return ; }
212+ let Some ( callee_obj) = callee. child_by_field_name ( "object" ) else { return } ;
213+ if node_text ( & callee_obj, source) != "Object" { return ; }
214+ let Some ( callee_prop) = callee. child_by_field_name ( "property" ) else { return } ;
215+ let method = node_text ( & callee_prop, source) ;
216+ if method != "defineProperty" && method != "defineProperties" { return ; }
217+
218+ let args_node = node. child_by_field_name ( "arguments" )
219+ . or_else ( || find_child ( node, "arguments" ) ) ;
220+ let Some ( args_node) = args_node else { return } ;
221+
222+ // Collect non-punctuation argument nodes in order
223+ let mut args: Vec < Node > = Vec :: new ( ) ;
224+ for i in 0 ..args_node. child_count ( ) {
225+ let Some ( child) = args_node. child ( i) else { continue } ;
226+ if !matches ! ( child. kind( ) , "(" | ")" | "," ) {
227+ args. push ( child) ;
228+ }
229+ }
230+
231+ if method == "defineProperty" {
232+ // Object.defineProperty(obj, "key", { value: fn })
233+ if args. len ( ) < 3 { return ; }
234+ if args[ 0 ] . kind ( ) != "identifier" { return ; }
235+ let obj_name = node_text ( & args[ 0 ] , source) ;
236+ let Some ( key) = extract_string_fragment ( & args[ 1 ] , source) else { return } ;
237+ let Some ( target) = find_descriptor_value ( & args[ 2 ] , source) else { return } ;
238+ symbols. type_map . push ( TypeMapEntry {
239+ name : format ! ( "{}.{}" , obj_name, key) ,
240+ type_name : target. to_string ( ) ,
241+ confidence : 0.85 ,
242+ } ) ;
243+ } else {
244+ // Object.defineProperties(obj, { "key": { value: fn }, ... })
245+ if args. len ( ) < 2 { return ; }
246+ if args[ 0 ] . kind ( ) != "identifier" { return ; }
247+ let obj_name = node_text ( & args[ 0 ] , source) . to_string ( ) ;
248+ if args[ 1 ] . kind ( ) != "object" { return ; }
249+ seed_descriptor_object ( & obj_name, & args[ 1 ] , source, symbols) ;
250+ }
251+ }
252+
253+ /// Seed composite pts keys from `const obj = Object.create({ f1, f2 })`.
254+ fn seed_object_create_entries ( var_name : & str , call_node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
255+ let Some ( callee) = call_node. child_by_field_name ( "function" ) else { return } ;
256+ if callee. kind ( ) != "member_expression" { return ; }
257+ let Some ( callee_obj) = callee. child_by_field_name ( "object" ) else { return } ;
258+ if node_text ( & callee_obj, source) != "Object" { return ; }
259+ let Some ( callee_prop) = callee. child_by_field_name ( "property" ) else { return } ;
260+ if node_text ( & callee_prop, source) != "create" { return ; }
261+
262+ let args_node = call_node. child_by_field_name ( "arguments" )
263+ . or_else ( || find_child ( call_node, "arguments" ) ) ;
264+ let Some ( args_node) = args_node else { return } ;
265+
266+ // First non-punctuation argument = prototype object
267+ let proto = ( 0 ..args_node. child_count ( ) )
268+ . filter_map ( |i| args_node. child ( i) )
269+ . find ( |n| !matches ! ( n. kind( ) , "(" | ")" | "," ) ) ;
270+ let Some ( proto) = proto else { return } ;
271+ if proto. kind ( ) != "object" { return } ;
272+
273+ for i in 0 ..proto. child_count ( ) {
274+ let Some ( child) = proto. child ( i) else { continue } ;
275+ match child. kind ( ) {
276+ "shorthand_property_identifier" => {
277+ // { f1 } shorthand — property name equals value name
278+ let name = node_text ( & child, source) ;
279+ symbols. type_map . push ( TypeMapEntry {
280+ name : format ! ( "{}.{}" , var_name, name) ,
281+ type_name : name. to_string ( ) ,
282+ confidence : 0.85 ,
283+ } ) ;
284+ }
285+ "pair" => {
286+ let Some ( key_n) = child. child_by_field_name ( "key" ) else { continue } ;
287+ let Some ( val_n) = child. child_by_field_name ( "value" ) else { continue } ;
288+ if val_n. kind ( ) != "identifier" { continue ; }
289+ let key = if key_n. kind ( ) == "string" {
290+ extract_string_fragment ( & key_n, source) . map ( |s| s. to_string ( ) )
291+ } else {
292+ Some ( node_text ( & key_n, source) . to_string ( ) )
293+ } ;
294+ let Some ( key) = key else { continue } ;
295+ symbols. type_map . push ( TypeMapEntry {
296+ name : format ! ( "{}.{}" , var_name, key) ,
297+ type_name : node_text ( & val_n, source) . to_string ( ) ,
298+ confidence : 0.85 ,
299+ } ) ;
300+ }
301+ _ => { }
302+ }
303+ }
304+ }
305+
306+ /// Iterate over the properties of a `defineProperties` descriptor object and seed the type_map.
307+ fn seed_descriptor_object ( obj_name : & str , obj_node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
308+ for i in 0 ..obj_node. child_count ( ) {
309+ let Some ( child) = obj_node. child ( i) else { continue } ;
310+ if child. kind ( ) != "pair" { continue ; }
311+ let Some ( key_n) = child. child_by_field_name ( "key" ) else { continue } ;
312+ let Some ( val_n) = child. child_by_field_name ( "value" ) else { continue } ;
313+ let key = if key_n. kind ( ) == "string" {
314+ extract_string_fragment ( & key_n, source) . map ( |s| s. to_string ( ) )
315+ } else {
316+ Some ( node_text ( & key_n, source) . to_string ( ) )
317+ } ;
318+ let Some ( key) = key else { continue } ;
319+ let Some ( target) = find_descriptor_value ( & val_n, source) else { continue } ;
320+ symbols. type_map . push ( TypeMapEntry {
321+ name : format ! ( "{}.{}" , obj_name, key) ,
322+ type_name : target. to_string ( ) ,
323+ confidence : 0.85 ,
324+ } ) ;
325+ }
326+ }
327+
328+ /// Extract the text of the `string_fragment` child of a string node, i.e. content without quotes.
329+ fn extract_string_fragment < ' a > ( node : & Node < ' a > , source : & ' a [ u8 ] ) -> Option < & ' a str > {
330+ if node. kind ( ) != "string" { return None ; }
331+ find_child ( node, "string_fragment" ) . map ( |n| node_text ( & n, source) )
332+ }
333+
334+ /// Find the `value` identifier in a property descriptor object `{ value: fn }`.
335+ fn find_descriptor_value < ' a > ( node : & Node < ' a > , source : & ' a [ u8 ] ) -> Option < & ' a str > {
336+ if node. kind ( ) != "object" { return None ; }
337+ for i in 0 ..node. child_count ( ) {
338+ let Some ( child) = node. child ( i) else { continue } ;
339+ if child. kind ( ) != "pair" { continue ; }
340+ let Some ( key) = child. child_by_field_name ( "key" ) else { continue } ;
341+ if node_text ( & key, source) != "value" { continue ; }
342+ let Some ( val) = child. child_by_field_name ( "value" ) else { continue } ;
343+ if val. kind ( ) == "identifier" {
344+ return Some ( node_text ( & val, source) ) ;
345+ }
346+ }
347+ None
348+ }
349+
197350// ── Return-type map extraction (Phase 8.2 parity) ───────────────────────────
198351
199352/// Walk the AST collecting function/method return types into `symbols.return_type_map`.
200353/// Mirrors `extractReturnTypeMapWalk` in src/extractors/javascript.ts.
201354fn match_js_return_type_map ( node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols , _depth : usize ) {
202355 match node. kind ( ) {
203- "function_declaration" => {
356+ "function_declaration" | "generator_function_declaration" => {
204357 let Some ( name_n) = node. child_by_field_name ( "name" ) else { return } ;
205358 let fn_name = node_text ( & name_n, source) ;
206359 if fn_name == "constructor" { return ; }
@@ -228,9 +381,9 @@ fn match_js_return_type_map(node: &Node, source: &[u8], symbols: &mut FileSymbol
228381 let Some ( name_n) = node. child_by_field_name ( "name" ) else { return } ;
229382 if name_n. kind ( ) != "identifier" { return ; }
230383 let Some ( value_n) = node. child_by_field_name ( "value" ) else { return } ;
231- // Only arrow_function and function_expression match the TS reference;
384+ // Only arrow_function, function_expression and generator_function match the TS reference;
232385 // "function" is not a valid tree-sitter value-expression kind here.
233- if !matches ! ( value_n. kind( ) , "arrow_function" | "function_expression" ) {
386+ if !matches ! ( value_n. kind( ) , "arrow_function" | "function_expression" | "generator_function" ) {
234387 return ;
235388 }
236389 let var_name = node_text ( & name_n, source) ;
@@ -334,7 +487,7 @@ fn match_js_call_assignments(node: &Node, source: &[u8], symbols: &mut FileSymbo
334487
335488fn match_js_node ( node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols , _depth : usize ) {
336489 match node. kind ( ) {
337- "function_declaration" => handle_function_decl ( node, source, symbols) ,
490+ "function_declaration" | "generator_function_declaration" => handle_function_decl ( node, source, symbols) ,
338491 "class_declaration" | "abstract_class_declaration" => {
339492 handle_class_decl ( node, source, symbols)
340493 }
@@ -497,7 +650,7 @@ fn handle_var_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
497650 let value_n = declarator. child_by_field_name ( "value" ) ;
498651 let ( Some ( name_n) , Some ( value_n) ) = ( name_n, value_n) else { continue } ;
499652 let vt = value_n. kind ( ) ;
500- if vt == "arrow_function" || vt == "function_expression" || vt == "function" {
653+ if vt == "arrow_function" || vt == "function_expression" || vt == "function" || vt == "generator_function" {
501654 let children = extract_js_parameters ( & value_n, source) ;
502655 symbols. definitions . push ( Definition {
503656 name : node_text ( & name_n, source) . to_string ( ) ,
@@ -651,7 +804,7 @@ fn handle_export_stmt(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
651804
652805fn handle_export_declaration ( node : & Node , decl : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
653806 let ( kind_str, field) = match decl. kind ( ) {
654- "function_declaration" => ( "function" , "name" ) ,
807+ "function_declaration" | "generator_function_declaration" => ( "function" , "name" ) ,
655808 "class_declaration" | "abstract_class_declaration" => ( "class" , "name" ) ,
656809 "interface_declaration" => ( "interface" , "name" ) ,
657810 "type_alias_declaration" => ( "type" , "name" ) ,
@@ -2292,4 +2445,76 @@ mod tests {
22922445 "compute call should have receiver='calc'"
22932446 ) ;
22942447 }
2448+
2449+ /// Phase 8.3e: Object.defineProperty seeds composite type_map key.
2450+ #[ test]
2451+ fn type_map_from_define_property ( ) {
2452+ let s = parse_js (
2453+ "function f1() {}\n \
2454+ const obj = {};\n \
2455+ Object.defineProperty(obj, \" f\" , { value: f1 });",
2456+ ) ;
2457+ let entry = s. type_map . iter ( ) . find ( |e| e. name == "obj.f" ) ;
2458+ assert ! ( entry. is_some( ) , "type_map should contain 'obj.f'; got: {:?}" , s. type_map) ;
2459+ assert_eq ! ( entry. unwrap( ) . type_name, "f1" ) ;
2460+ }
2461+
2462+ /// Phase 8.3e: Object.defineProperties seeds composite type_map keys.
2463+ #[ test]
2464+ fn type_map_from_define_properties ( ) {
2465+ let s = parse_js (
2466+ "function f1() {}\n \
2467+ function f2() {}\n \
2468+ const obj = {};\n \
2469+ Object.defineProperties(obj, {\n \
2470+ \" f1\" : { value: f1 },\n \
2471+ \" f2\" : { value: f2 },\n \
2472+ });",
2473+ ) ;
2474+ let e1 = s. type_map . iter ( ) . find ( |e| e. name == "obj.f1" ) ;
2475+ let e2 = s. type_map . iter ( ) . find ( |e| e. name == "obj.f2" ) ;
2476+ assert ! ( e1. is_some( ) , "type_map should contain 'obj.f1'; got: {:?}" , s. type_map) ;
2477+ assert ! ( e2. is_some( ) , "type_map should contain 'obj.f2'; got: {:?}" , s. type_map) ;
2478+ assert_eq ! ( e1. unwrap( ) . type_name, "f1" ) ;
2479+ assert_eq ! ( e2. unwrap( ) . type_name, "f2" ) ;
2480+ }
2481+
2482+ /// Phase 8.3e: Object.create seeds composite type_map keys from shorthand proto.
2483+ #[ test]
2484+ fn type_map_from_object_create ( ) {
2485+ let s = parse_js (
2486+ "function f1() {}\n \
2487+ function f2() {}\n \
2488+ const obj = Object.create({ f1, f2 });",
2489+ ) ;
2490+ let e1 = s. type_map . iter ( ) . find ( |e| e. name == "obj.f1" ) ;
2491+ let e2 = s. type_map . iter ( ) . find ( |e| e. name == "obj.f2" ) ;
2492+ assert ! ( e1. is_some( ) , "type_map should contain 'obj.f1'; got: {:?}" , s. type_map) ;
2493+ assert ! ( e2. is_some( ) , "type_map should contain 'obj.f2'; got: {:?}" , s. type_map) ;
2494+ assert_eq ! ( e1. unwrap( ) . type_name, "f1" ) ;
2495+ assert_eq ! ( e2. unwrap( ) . type_name, "f2" ) ;
2496+ }
2497+
2498+ /// Phase 8.3e: call receiver is correctly recorded for obj.f() inside defProp body.
2499+ #[ test]
2500+ fn call_receiver_for_define_property ( ) {
2501+ let s = parse_js (
2502+ "function f1() {}\n \
2503+ function defProp() {\n \
2504+ const obj = {};\n \
2505+ Object.defineProperty(obj, \" f\" , { value: f1 });\n \
2506+ obj.f();\n \
2507+ }",
2508+ ) ;
2509+ let tm = s. type_map . iter ( ) . find ( |e| e. name == "obj.f" ) ;
2510+ assert ! ( tm. is_some( ) , "type_map should contain 'obj.f'; got: {:?}" , s. type_map) ;
2511+ assert_eq ! ( tm. unwrap( ) . type_name, "f1" ) ;
2512+
2513+ let call = s. calls . iter ( ) . find ( |c| c. name == "f" && c. receiver . as_deref ( ) == Some ( "obj" ) ) ;
2514+ assert ! (
2515+ call. is_some( ) ,
2516+ "calls should contain obj.f() with receiver='obj'; got: {:?}" ,
2517+ s. calls. iter( ) . map( |c| ( & c. name, & c. receiver) ) . collect:: <Vec <_>>( )
2518+ ) ;
2519+ }
22952520}
0 commit comments