@@ -407,13 +407,36 @@ fn resolve_call_targets<'a>(
407407 } ;
408408 let type_lookup = type_map. get ( effective_receiver)
409409 . or_else ( || type_map. get ( receiver. as_str ( ) ) ) ;
410- if let Some ( & ( type_name, _conf) ) = type_lookup {
410+ // Inline new-expression receiver: `(new Foo).bar()` — extract the constructor name
411+ // when no typeMap entry exists for the complex receiver expression.
412+ // Mirrors the regex `/^\(?\s*new\s+([A-Z_$][A-Za-z0-9_$]*)/` in call-resolver.ts.
413+ let inline_new_type = if type_lookup. is_none ( ) {
414+ extract_inline_new_type ( receiver)
415+ } else {
416+ None
417+ } ;
418+ // Use typeMap-resolved type or inline-new-extracted type, whichever is available.
419+ let resolved_type = type_lookup. map ( |& ( t, _) | t) . or ( inline_new_type. as_deref ( ) ) ;
420+ if let Some ( type_name) = resolved_type {
411421 let qualified = format ! ( "{}.{}" , type_name, call. name) ;
412422 let typed: Vec < & NodeInfo > = ctx. nodes_by_name
413423 . get ( qualified. as_str ( ) )
414424 . map ( |v| v. iter ( ) . filter ( |n| n. kind == "method" ) . copied ( ) . collect ( ) )
415425 . unwrap_or_default ( ) ;
416426 if !typed. is_empty ( ) { return typed; }
427+ // Prototype alias: `Foo.prototype.bar = identifier` seeds typeMap['Foo.bar'] = identifier.
428+ // After the direct method lookup misses (no definition emitted for this method),
429+ // check if the typeMap holds an alias to a standalone function.
430+ // Mirrors the protoAlias fallback in resolveByMethodOrGlobal in call-resolver.ts.
431+ if let Some ( & ( proto_target, _) ) = type_map. get ( qualified. as_str ( ) ) {
432+ let resolved: Vec < & NodeInfo > = ctx. nodes_by_name
433+ . get ( proto_target)
434+ . map ( |v| v. iter ( )
435+ . filter ( |n| import_resolution:: compute_confidence ( rel_path, & n. file , None ) >= 0.5 )
436+ . copied ( ) . collect ( ) )
437+ . unwrap_or_default ( ) ;
438+ if !resolved. is_empty ( ) { return resolved; }
439+ }
417440 }
418441 // 4.5. Phase 8.3d: composite pts key — `obj.prop = fn` seeds typeMap['obj.prop']
419442 let composite_key = format ! ( "{}.{}" , receiver, call. name) ;
@@ -510,6 +533,31 @@ fn resolve_call_targets<'a>(
510533 Vec :: new ( )
511534}
512535
536+ /// Extract the constructor name from an inline `new` receiver expression.
537+ ///
538+ /// Mirrors the regex `/^\(?\s*new\s+([A-Z_$][A-Za-z0-9_$]*)/` used in call-resolver.ts.
539+ /// Handles `(new Foo)` and `(new Foo('arg'))` receivers that arise when the call site
540+ /// is `(new Foo).method()` without a named variable binding.
541+ ///
542+ /// Only extracts names that start with an uppercase letter, `_`, or `$` to avoid
543+ /// false positives on plain lowercase constructor calls (rare but present in legacy code).
544+ fn extract_inline_new_type ( receiver : & str ) -> Option < String > {
545+ let s = receiver. strip_prefix ( '(' ) . unwrap_or ( receiver) . trim_start ( ) ;
546+ let s = s. strip_prefix ( "new" ) ?;
547+ if !s. starts_with ( |c : char | c. is_whitespace ( ) ) { return None ; }
548+ let s = s. trim_start ( ) ;
549+ let end = s. find ( |c : char | !c. is_alphanumeric ( ) && c != '_' && c != '$' )
550+ . unwrap_or ( s. len ( ) ) ;
551+ let name = & s[ ..end] ;
552+ if name. is_empty ( ) { return None ; }
553+ let first = name. chars ( ) . next ( ) ?;
554+ if first. is_uppercase ( ) || first == '_' || first == '$' {
555+ Some ( name. to_string ( ) )
556+ } else {
557+ None
558+ }
559+ }
560+
513561/// Sort targets by confidence descending.
514562fn sort_targets_by_confidence ( targets : & mut Vec < & NodeInfo > , rel_path : & str , imported_from : Option < & str > ) {
515563 if targets. len ( ) > 1 {
@@ -1391,3 +1439,52 @@ mod call_edge_tests {
13911439 assert_eq ! ( receiver_edge. unwrap( ) . target_id, 2 ) ;
13921440 }
13931441}
1442+
1443+ #[ cfg( test) ]
1444+ mod inline_new_type_tests {
1445+ use super :: extract_inline_new_type;
1446+
1447+ #[ test]
1448+ fn parens_new_uppercase ( ) {
1449+ assert_eq ! ( extract_inline_new_type( "(new Foo)" ) , Some ( "Foo" . to_string( ) ) ) ;
1450+ }
1451+
1452+ #[ test]
1453+ fn parens_new_with_args ( ) {
1454+ // (new Foo('arg')) — parens and constructor args
1455+ assert_eq ! ( extract_inline_new_type( "(new Foo('arg'))" ) , Some ( "Foo" . to_string( ) ) ) ;
1456+ }
1457+
1458+ #[ test]
1459+ fn no_parens_new_uppercase ( ) {
1460+ assert_eq ! ( extract_inline_new_type( "new Bar" ) , Some ( "Bar" . to_string( ) ) ) ;
1461+ }
1462+
1463+ #[ test]
1464+ fn underscore_prefix_accepted ( ) {
1465+ assert_eq ! ( extract_inline_new_type( "new _Factory" ) , Some ( "_Factory" . to_string( ) ) ) ;
1466+ }
1467+
1468+ #[ test]
1469+ fn dollar_prefix_accepted ( ) {
1470+ assert_eq ! ( extract_inline_new_type( "new $Service" ) , Some ( "$Service" . to_string( ) ) ) ;
1471+ }
1472+
1473+ #[ test]
1474+ fn lowercase_constructor_rejected ( ) {
1475+ // `new foo()` — lowercase, should return None to avoid false positives
1476+ assert_eq ! ( extract_inline_new_type( "new foo" ) , None ) ;
1477+ }
1478+
1479+ #[ test]
1480+ fn not_a_new_expression ( ) {
1481+ // plain receiver name — no `new` keyword
1482+ assert_eq ! ( extract_inline_new_type( "myVar" ) , None ) ;
1483+ }
1484+
1485+ #[ test]
1486+ fn new_without_whitespace_is_not_new_keyword ( ) {
1487+ // `newFoo` — not a `new` keyword, just an identifier
1488+ assert_eq ! ( extract_inline_new_type( "newFoo" ) , None ) ;
1489+ }
1490+ }
0 commit comments