Skip to content

Commit d1d5f56

Browse files
authored
Merge branch 'main' into feat/defineProperty-accessor-this-dispatch-1335
2 parents 877d536 + 428edb4 commit d1d5f56

54 files changed

Lines changed: 2164 additions & 89 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

biome.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,13 @@
2525
"semicolons": "always",
2626
"trailingCommas": "all"
2727
}
28-
}
28+
},
29+
"overrides": [
30+
{
31+
"includes": ["tests/benchmarks/resolution/fixtures/**"],
32+
"linter": {
33+
"enabled": false
34+
}
35+
}
36+
]
2937
}

crates/codegraph-core/src/edge_builder.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.
514562
fn 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

Comments
 (0)