@@ -95,10 +95,18 @@ fn match_js_type_map(node: &Node, source: &[u8], symbols: &mut FileSymbols, _dep
9595 } ) ;
9696 }
9797 }
98+ // Phase 8.3e: Object.create({ key: fn }) → composite pts key per property
99+ if value_n. kind ( ) == "call_expression" {
100+ seed_object_create_entries ( var_name, & value_n, source, symbols) ;
101+ }
98102 }
99103 }
100104 }
101105 }
106+ // Phase 8.3e: Object.defineProperty / defineProperties → composite pts key
107+ "call_expression" => {
108+ seed_define_property_entries ( node, source, symbols) ;
109+ }
102110 "required_parameter" | "optional_parameter" => {
103111 let name_node = node. child_by_field_name ( "pattern" )
104112 . or_else ( || node. child_by_field_name ( "left" ) )
@@ -196,13 +204,158 @@ fn is_js_builtin_global(name: &str) -> bool {
196204 )
197205}
198206
207+ // ── Phase 8.3e: Object.defineProperty / defineProperties / create ────────────
208+
209+ /// Seed composite pts keys for `Object.defineProperty(obj, "key", { value: fn })`
210+ /// and `Object.defineProperties(obj, { "key": { value: fn }, ... })`.
211+ fn seed_define_property_entries ( node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
212+ let Some ( callee) = node. child_by_field_name ( "function" ) else { return } ;
213+ if callee. kind ( ) != "member_expression" { return ; }
214+ let Some ( callee_obj) = callee. child_by_field_name ( "object" ) else { return } ;
215+ if node_text ( & callee_obj, source) != "Object" { return ; }
216+ let Some ( callee_prop) = callee. child_by_field_name ( "property" ) else { return } ;
217+ let method = node_text ( & callee_prop, source) ;
218+ if method != "defineProperty" && method != "defineProperties" { return ; }
219+
220+ let args_node = node. child_by_field_name ( "arguments" )
221+ . or_else ( || find_child ( node, "arguments" ) ) ;
222+ let Some ( args_node) = args_node else { return } ;
223+
224+ // Collect non-punctuation argument nodes in order
225+ let mut args: Vec < Node > = Vec :: new ( ) ;
226+ for i in 0 ..args_node. child_count ( ) {
227+ let Some ( child) = args_node. child ( i) else { continue } ;
228+ if !matches ! ( child. kind( ) , "(" | ")" | "," ) {
229+ args. push ( child) ;
230+ }
231+ }
232+
233+ if method == "defineProperty" {
234+ // Object.defineProperty(obj, "key", { value: fn })
235+ if args. len ( ) < 3 { return ; }
236+ if args[ 0 ] . kind ( ) != "identifier" { return ; }
237+ let obj_name = node_text ( & args[ 0 ] , source) ;
238+ let Some ( key) = extract_string_fragment ( & args[ 1 ] , source) else { return } ;
239+ let Some ( target) = find_descriptor_value ( & args[ 2 ] , source) else { return } ;
240+ symbols. type_map . push ( TypeMapEntry {
241+ name : format ! ( "{}.{}" , obj_name, key) ,
242+ type_name : target. to_string ( ) ,
243+ confidence : 0.85 ,
244+ } ) ;
245+ } else {
246+ // Object.defineProperties(obj, { "key": { value: fn }, ... })
247+ if args. len ( ) < 2 { return ; }
248+ if args[ 0 ] . kind ( ) != "identifier" { return ; }
249+ let obj_name = node_text ( & args[ 0 ] , source) . to_string ( ) ;
250+ if args[ 1 ] . kind ( ) != "object" { return ; }
251+ seed_descriptor_object ( & obj_name, & args[ 1 ] , source, symbols) ;
252+ }
253+ }
254+
255+ /// Seed composite pts keys from `const obj = Object.create({ f1, f2 })`.
256+ fn seed_object_create_entries ( var_name : & str , call_node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
257+ let Some ( callee) = call_node. child_by_field_name ( "function" ) else { return } ;
258+ if callee. kind ( ) != "member_expression" { return ; }
259+ let Some ( callee_obj) = callee. child_by_field_name ( "object" ) else { return } ;
260+ if node_text ( & callee_obj, source) != "Object" { return ; }
261+ let Some ( callee_prop) = callee. child_by_field_name ( "property" ) else { return } ;
262+ if node_text ( & callee_prop, source) != "create" { return ; }
263+
264+ let args_node = call_node. child_by_field_name ( "arguments" )
265+ . or_else ( || find_child ( call_node, "arguments" ) ) ;
266+ let Some ( args_node) = args_node else { return } ;
267+
268+ // First non-punctuation argument = prototype object
269+ let proto = ( 0 ..args_node. child_count ( ) )
270+ . filter_map ( |i| args_node. child ( i) )
271+ . find ( |n| !matches ! ( n. kind( ) , "(" | ")" | "," ) ) ;
272+ let Some ( proto) = proto else { return } ;
273+ if proto. kind ( ) != "object" { return } ;
274+
275+ for i in 0 ..proto. child_count ( ) {
276+ let Some ( child) = proto. child ( i) else { continue } ;
277+ match child. kind ( ) {
278+ "shorthand_property_identifier" => {
279+ // { f1 } shorthand — property name equals value name
280+ let name = node_text ( & child, source) ;
281+ symbols. type_map . push ( TypeMapEntry {
282+ name : format ! ( "{}.{}" , var_name, name) ,
283+ type_name : name. to_string ( ) ,
284+ confidence : 0.85 ,
285+ } ) ;
286+ }
287+ "pair" => {
288+ let Some ( key_n) = child. child_by_field_name ( "key" ) else { continue } ;
289+ let Some ( val_n) = child. child_by_field_name ( "value" ) else { continue } ;
290+ if val_n. kind ( ) != "identifier" { continue ; }
291+ let key = if key_n. kind ( ) == "string" {
292+ extract_string_fragment ( & key_n, source) . map ( |s| s. to_string ( ) )
293+ } else {
294+ Some ( node_text ( & key_n, source) . to_string ( ) )
295+ } ;
296+ let Some ( key) = key else { continue } ;
297+ symbols. type_map . push ( TypeMapEntry {
298+ name : format ! ( "{}.{}" , var_name, key) ,
299+ type_name : node_text ( & val_n, source) . to_string ( ) ,
300+ confidence : 0.85 ,
301+ } ) ;
302+ }
303+ _ => { }
304+ }
305+ }
306+ }
307+
308+ /// Iterate over the properties of a `defineProperties` descriptor object and seed the type_map.
309+ fn seed_descriptor_object ( obj_name : & str , obj_node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
310+ for i in 0 ..obj_node. child_count ( ) {
311+ let Some ( child) = obj_node. child ( i) else { continue } ;
312+ if child. kind ( ) != "pair" { continue ; }
313+ let Some ( key_n) = child. child_by_field_name ( "key" ) else { continue } ;
314+ let Some ( val_n) = child. child_by_field_name ( "value" ) else { continue } ;
315+ let key = if key_n. kind ( ) == "string" {
316+ extract_string_fragment ( & key_n, source) . map ( |s| s. to_string ( ) )
317+ } else {
318+ Some ( node_text ( & key_n, source) . to_string ( ) )
319+ } ;
320+ let Some ( key) = key else { continue } ;
321+ let Some ( target) = find_descriptor_value ( & val_n, source) else { continue } ;
322+ symbols. type_map . push ( TypeMapEntry {
323+ name : format ! ( "{}.{}" , obj_name, key) ,
324+ type_name : target. to_string ( ) ,
325+ confidence : 0.85 ,
326+ } ) ;
327+ }
328+ }
329+
330+ /// Extract the text of the `string_fragment` child of a string node, i.e. content without quotes.
331+ fn extract_string_fragment < ' a > ( node : & Node < ' a > , source : & ' a [ u8 ] ) -> Option < & ' a str > {
332+ if node. kind ( ) != "string" { return None ; }
333+ find_child ( node, "string_fragment" ) . map ( |n| node_text ( & n, source) )
334+ }
335+
336+ /// Find the `value` identifier in a property descriptor object `{ value: fn }`.
337+ fn find_descriptor_value < ' a > ( node : & Node < ' a > , source : & ' a [ u8 ] ) -> Option < & ' a str > {
338+ if node. kind ( ) != "object" { return None ; }
339+ for i in 0 ..node. child_count ( ) {
340+ let Some ( child) = node. child ( i) else { continue } ;
341+ if child. kind ( ) != "pair" { continue ; }
342+ let Some ( key) = child. child_by_field_name ( "key" ) else { continue } ;
343+ if node_text ( & key, source) != "value" { continue ; }
344+ let Some ( val) = child. child_by_field_name ( "value" ) else { continue } ;
345+ if val. kind ( ) == "identifier" {
346+ return Some ( node_text ( & val, source) ) ;
347+ }
348+ }
349+ None
350+ }
351+
199352// ── Return-type map extraction (Phase 8.2 parity) ───────────────────────────
200353
201354/// Walk the AST collecting function/method return types into `symbols.return_type_map`.
202355/// Mirrors `extractReturnTypeMapWalk` in src/extractors/javascript.ts.
203356fn match_js_return_type_map ( node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols , _depth : usize ) {
204357 match node. kind ( ) {
205- "function_declaration" => {
358+ "function_declaration" | "generator_function_declaration" => {
206359 let Some ( name_n) = node. child_by_field_name ( "name" ) else { return } ;
207360 let fn_name = node_text ( & name_n, source) ;
208361 if fn_name == "constructor" { return ; }
@@ -230,9 +383,9 @@ fn match_js_return_type_map(node: &Node, source: &[u8], symbols: &mut FileSymbol
230383 let Some ( name_n) = node. child_by_field_name ( "name" ) else { return } ;
231384 if name_n. kind ( ) != "identifier" { return ; }
232385 let Some ( value_n) = node. child_by_field_name ( "value" ) else { return } ;
233- // Only arrow_function and function_expression match the TS reference;
386+ // Only arrow_function, function_expression and generator_function match the TS reference;
234387 // "function" is not a valid tree-sitter value-expression kind here.
235- if !matches ! ( value_n. kind( ) , "arrow_function" | "function_expression" ) {
388+ if !matches ! ( value_n. kind( ) , "arrow_function" | "function_expression" | "generator_function" ) {
236389 return ;
237390 }
238391 let var_name = node_text ( & name_n, source) ;
@@ -479,7 +632,7 @@ fn match_js_call_assignments(node: &Node, source: &[u8], symbols: &mut FileSymbo
479632
480633fn match_js_node ( node : & Node , source : & [ u8 ] , symbols : & mut FileSymbols , _depth : usize ) {
481634 match node. kind ( ) {
482- "function_declaration" => handle_function_decl ( node, source, symbols) ,
635+ "function_declaration" | "generator_function_declaration" => handle_function_decl ( node, source, symbols) ,
483636 "class_declaration" | "abstract_class_declaration" => {
484637 handle_class_decl ( node, source, symbols)
485638 }
@@ -642,7 +795,7 @@ fn handle_var_decl(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
642795 let value_n = declarator. child_by_field_name ( "value" ) ;
643796 let ( Some ( name_n) , Some ( value_n) ) = ( name_n, value_n) else { continue } ;
644797 let vt = value_n. kind ( ) ;
645- if vt == "arrow_function" || vt == "function_expression" || vt == "function" {
798+ if vt == "arrow_function" || vt == "function_expression" || vt == "function" || vt == "generator_function" {
646799 let children = extract_js_parameters ( & value_n, source) ;
647800 symbols. definitions . push ( Definition {
648801 name : node_text ( & name_n, source) . to_string ( ) ,
@@ -796,7 +949,7 @@ fn handle_export_stmt(node: &Node, source: &[u8], symbols: &mut FileSymbols) {
796949
797950fn handle_export_declaration ( node : & Node , decl : & Node , source : & [ u8 ] , symbols : & mut FileSymbols ) {
798951 let ( kind_str, field) = match decl. kind ( ) {
799- "function_declaration" => ( "function" , "name" ) ,
952+ "function_declaration" | "generator_function_declaration" => ( "function" , "name" ) ,
800953 "class_declaration" | "abstract_class_declaration" => ( "class" , "name" ) ,
801954 "interface_declaration" => ( "interface" , "name" ) ,
802955 "type_alias_declaration" => ( "type" , "name" ) ,
@@ -2537,4 +2690,77 @@ mod tests {
25372690 let def = s. definitions . iter ( ) . find ( |d| d. name . contains ( "Array" ) ) ;
25382691 assert ! ( def. is_none( ) , "built-in prototype assignment should be ignored; got: {:?}" , def) ;
25392692 }
2693+
2694+
2695+ /// Phase 8.3e: Object.defineProperty seeds composite type_map key.
2696+ #[ test]
2697+ fn type_map_from_define_property ( ) {
2698+ let s = parse_js (
2699+ "function f1() {}\n \
2700+ const obj = {};\n \
2701+ Object.defineProperty(obj, \" f\" , { value: f1 });",
2702+ ) ;
2703+ let entry = s. type_map . iter ( ) . find ( |e| e. name == "obj.f" ) ;
2704+ assert ! ( entry. is_some( ) , "type_map should contain 'obj.f'; got: {:?}" , s. type_map) ;
2705+ assert_eq ! ( entry. unwrap( ) . type_name, "f1" ) ;
2706+ }
2707+
2708+ /// Phase 8.3e: Object.defineProperties seeds composite type_map keys.
2709+ #[ test]
2710+ fn type_map_from_define_properties ( ) {
2711+ let s = parse_js (
2712+ "function f1() {}\n \
2713+ function f2() {}\n \
2714+ const obj = {};\n \
2715+ Object.defineProperties(obj, {\n \
2716+ \" f1\" : { value: f1 },\n \
2717+ \" f2\" : { value: f2 },\n \
2718+ });",
2719+ ) ;
2720+ let e1 = s. type_map . iter ( ) . find ( |e| e. name == "obj.f1" ) ;
2721+ let e2 = s. type_map . iter ( ) . find ( |e| e. name == "obj.f2" ) ;
2722+ assert ! ( e1. is_some( ) , "type_map should contain 'obj.f1'; got: {:?}" , s. type_map) ;
2723+ assert ! ( e2. is_some( ) , "type_map should contain 'obj.f2'; got: {:?}" , s. type_map) ;
2724+ assert_eq ! ( e1. unwrap( ) . type_name, "f1" ) ;
2725+ assert_eq ! ( e2. unwrap( ) . type_name, "f2" ) ;
2726+ }
2727+
2728+ /// Phase 8.3e: Object.create seeds composite type_map keys from shorthand proto.
2729+ #[ test]
2730+ fn type_map_from_object_create ( ) {
2731+ let s = parse_js (
2732+ "function f1() {}\n \
2733+ function f2() {}\n \
2734+ const obj = Object.create({ f1, f2 });",
2735+ ) ;
2736+ let e1 = s. type_map . iter ( ) . find ( |e| e. name == "obj.f1" ) ;
2737+ let e2 = s. type_map . iter ( ) . find ( |e| e. name == "obj.f2" ) ;
2738+ assert ! ( e1. is_some( ) , "type_map should contain 'obj.f1'; got: {:?}" , s. type_map) ;
2739+ assert ! ( e2. is_some( ) , "type_map should contain 'obj.f2'; got: {:?}" , s. type_map) ;
2740+ assert_eq ! ( e1. unwrap( ) . type_name, "f1" ) ;
2741+ assert_eq ! ( e2. unwrap( ) . type_name, "f2" ) ;
2742+ }
2743+
2744+ /// Phase 8.3e: call receiver is correctly recorded for obj.f() inside defProp body.
2745+ #[ test]
2746+ fn call_receiver_for_define_property ( ) {
2747+ let s = parse_js (
2748+ "function f1() {}\n \
2749+ function defProp() {\n \
2750+ const obj = {};\n \
2751+ Object.defineProperty(obj, \" f\" , { value: f1 });\n \
2752+ obj.f();\n \
2753+ }",
2754+ ) ;
2755+ let tm = s. type_map . iter ( ) . find ( |e| e. name == "obj.f" ) ;
2756+ assert ! ( tm. is_some( ) , "type_map should contain 'obj.f'; got: {:?}" , s. type_map) ;
2757+ assert_eq ! ( tm. unwrap( ) . type_name, "f1" ) ;
2758+
2759+ let call = s. calls . iter ( ) . find ( |c| c. name == "f" && c. receiver . as_deref ( ) == Some ( "obj" ) ) ;
2760+ assert ! (
2761+ call. is_some( ) ,
2762+ "calls should contain obj.f() with receiver='obj'; got: {:?}" ,
2763+ s. calls. iter( ) . map( |c| ( & c. name, & c. receiver) ) . collect:: <Vec <_>>( )
2764+ ) ;
2765+ }
25402766}
0 commit comments