mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 05:41:18 +08:00
fix(dart): index const constructors and redirecting factories; a call through a type stays on that type (#2380)
* fix(dart): index const constructors and redirecting factories; a call through a type stays on that type Both Dart extractors (the TS walker and the native kernel) skipped `const` constructors and redirecting factories (`const Foo.bar(...)`, `const factory Foo.bar() = _Bar;`), which parse as signature kinds of their own. A call such as flutter_bloc's `BlocProvider.value(...)` then found no method on BlocProvider, and the resolver guessed another type's `value`: on felangel/bloc, 89 calls linked to `RepositoryProvider::value`. Extraction (TS + kernel, at parity): - Named const constructors and redirecting factories are methods named by the constructor, returning their class, with a parameters-only signature; the unnamed ones stay unindexed. The name is read only from before the parameter list, so a redirect target (`= _Impl.named`) never names it. - A redirecting factory references the class it redirects to, not the target constructor's name or an import prefix. - A constructor called with type arguments keeps its type: `BlocProvider<CounterCubit>.value(...)` is `BlocProvider.value`, as a statement and as an expression (`constructor_invocation`, which produced no call at all before). Resolver: - A Dart call through a type's name reaches only that type's own constructor, static member or static constant, or nothing: no guess by name, no inherited lookup, no same-file look-alike, no instance member of the same name. SDK calls like `Uri.parse` no longer land on a project `parse` past an `extension on Uri`. - A named constructor is never reached by a bare name, the last link of a chain, a function value or an annotation (riverpod's 210 `@internal` annotations). Validated on felangel/bloc @b9be1e2 and rrousselGit/riverpod @4ba1be2 with the kernel loaded: bloc 18,338 -> 18,495 edges, riverpod 85,606 -> 85,854, every removed edge triaged. Afterwards every edge into a named constructor is written `Owner.ctor`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(dart): read a type's static members one way for calls and chains - A call through a type's name now finds its member with dartStaticMember, the lookup a chain's static link uses since #2376, so both pick the same member: the call site's own file first, then the nearest copy. - isDartStaticMember's constructor test is isDartConstructor, anchored at the declaration: an instance getter declared just above a same-named factory (`Object? get error` over `const factory AsyncValue.error(...)`) read the factory from the next lines and counted as static. - clearNameMatcherMemos drops DART_CONSTRUCTORS with the other source-derived Dart memos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+100
-19
@@ -18,8 +18,9 @@
|
||||
//! `.`); `ConfigT.load()` double-emits (calls + a static-member references
|
||||
//! ref — no callee-of-call skip in the dart branch); operator methods mint
|
||||
//! `method "<anonymous>"`; the unnamed constructor is skipped
|
||||
//! (isMisparsedFunction) while named ctors/factories are named by the CTOR
|
||||
//! name with the class as returnType; instance fields mint NO nodes (only
|
||||
//! (isMisparsedFunction) while named ctors/factories — `const` ones and
|
||||
//! redirecting factories included — are named by the CTOR name with the
|
||||
//! class as returnType; instance fields mint NO nodes (only
|
||||
//! static_final_declaration → constant, via the hook); every initializer is
|
||||
//! body-walked once — a constant's from the constant, a field's or a
|
||||
//! top-level variable's from the class or the file; prefixed return
|
||||
@@ -90,6 +91,23 @@ fn angle_args_re() -> &'static Regex {
|
||||
RE.get_or_init(|| Regex::new(r"<[^>]*>").unwrap())
|
||||
}
|
||||
|
||||
/// The UpperCamel test of isDartTypeName (`/^[_$]*[A-Z]/`) — how Dart writes
|
||||
/// a type, `_Private` and `$Generated` ones included.
|
||||
fn is_upper_camel(text: &str) -> bool {
|
||||
text.trim_start_matches(&['_', '$'][..])
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_ascii_uppercase())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// DART_BODILESS_CTORS (dart.ts) — `const Foo.bar();` and a redirecting
|
||||
/// factory `const factory Foo.bar() = _Bar;`, each a signature kind of its
|
||||
/// own inside a `declaration`.
|
||||
fn is_bodiless_ctor(kind: &str) -> bool {
|
||||
matches!(kind, "constant_constructor_signature" | "redirecting_factory_constructor_signature")
|
||||
}
|
||||
|
||||
struct Scope {
|
||||
row: u32,
|
||||
kind: &'static str,
|
||||
@@ -385,7 +403,9 @@ impl<'t> Walker<'t> {
|
||||
|
||||
/// dartConstructorSignature (dart.ts:25-35).
|
||||
fn constructor_signature(&self, node: Node<'t>) -> Option<Node<'t>> {
|
||||
if matches!(node.kind(), "factory_constructor_signature" | "constructor_signature") {
|
||||
if matches!(node.kind(), "factory_constructor_signature" | "constructor_signature")
|
||||
|| is_bodiless_ctor(node.kind())
|
||||
{
|
||||
return Some(node);
|
||||
}
|
||||
if node.kind() == "method_signature" {
|
||||
@@ -415,9 +435,12 @@ impl<'t> Walker<'t> {
|
||||
/// dartCtorInfo (dart.ts:61-70).
|
||||
fn ctor_info(&self, node: Node<'t>) -> Option<(String, String)> {
|
||||
let ctor = self.constructor_signature(node)?;
|
||||
// The names before the parameters: a redirecting factory names its
|
||||
// target after them (`factory Foo() = _Impl.named;`).
|
||||
let mut cursor = ctor.walk();
|
||||
let ids: Vec<Node<'t>> = ctor
|
||||
.named_children(&mut cursor)
|
||||
.take_while(|c| c.kind() != "formal_parameter_list")
|
||||
.filter(|c| c.kind() == "identifier")
|
||||
.collect();
|
||||
let class_name = self.enclosing_type_name(node)?;
|
||||
@@ -448,11 +471,13 @@ impl<'t> Walker<'t> {
|
||||
Some(last.to_string())
|
||||
}
|
||||
|
||||
/// isMisparsedFunction (dart.ts:177-188) — skip the UNNAMED constructor.
|
||||
fn is_unnamed_ctor(&self, node: Node<'t>) -> bool {
|
||||
/// isMisparsedFunction (dart.ts:177-188) — skip the UNNAMED constructor,
|
||||
/// and a `const` ctor / redirecting factory that names no enclosing type
|
||||
/// (error recovery's).
|
||||
fn is_misparsed_function(&self, node: Node<'t>) -> bool {
|
||||
match self.ctor_info(node) {
|
||||
Some((class_name, ctor_name)) => ctor_name == class_name,
|
||||
None => false,
|
||||
None => is_bodiless_ctor(node.kind()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,10 +488,15 @@ impl<'t> Walker<'t> {
|
||||
let params = sig
|
||||
.named_children(&mut c1)
|
||||
.find(|c| c.kind() == "formal_parameter_list");
|
||||
// A constructor has no return type: the type a redirecting factory
|
||||
// names is its target (`= _Impl`).
|
||||
let mut c2 = sig.walk();
|
||||
let ret = sig
|
||||
.named_children(&mut c2)
|
||||
.find(|c| matches!(c.kind(), "type_identifier" | "void_type"));
|
||||
let ret = if is_bodiless_ctor(sig.kind()) {
|
||||
None
|
||||
} else {
|
||||
sig.named_children(&mut c2)
|
||||
.find(|c| matches!(c.kind(), "type_identifier" | "void_type"))
|
||||
};
|
||||
if params.is_none() && ret.is_none() {
|
||||
return None;
|
||||
}
|
||||
@@ -704,7 +734,10 @@ impl<'t> Walker<'t> {
|
||||
self.extract_class(node);
|
||||
return;
|
||||
}
|
||||
"method_signature" | "constructor_signature" => {
|
||||
"method_signature"
|
||||
| "constructor_signature"
|
||||
| "constant_constructor_signature"
|
||||
| "redirecting_factory_constructor_signature" => {
|
||||
self.extract_method(node);
|
||||
return;
|
||||
}
|
||||
@@ -753,7 +786,7 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
// isMisparsedFunction: the unnamed constructor is skipped — node
|
||||
// suppressed, body still walked (attributed to the current stack top).
|
||||
if self.is_unnamed_ctor(node) {
|
||||
if self.is_misparsed_function(node) {
|
||||
if let Some(body) = self.resolve_body(node) {
|
||||
self.visit_body(body);
|
||||
}
|
||||
@@ -800,7 +833,7 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
let name = self.extract_name(node);
|
||||
// isMisparsedFunction — the unnamed ctor: body-only walk.
|
||||
if self.is_unnamed_ctor(node) {
|
||||
if self.is_misparsed_function(node) {
|
||||
if let Some(body) = self.resolve_body(node) {
|
||||
self.visit_body(body);
|
||||
}
|
||||
@@ -1035,6 +1068,11 @@ impl<'t> Walker<'t> {
|
||||
if ap.kind() == "identifier" {
|
||||
return Some(format!("{}.{}", self.text(ap), self.text(method_id)));
|
||||
}
|
||||
// A constructor called with type arguments names
|
||||
// its type all the same (`BlocProvider<T>.value`).
|
||||
if let Some(type_name) = self.type_arguments_receiver(ap) {
|
||||
return Some(format!("{}.{}", type_name, self.text(method_id)));
|
||||
}
|
||||
// Chained static-factory: the receiver is itself
|
||||
// a call — re-encode `<inner>().<method>` when
|
||||
// the chain starts capitalized (#750).
|
||||
@@ -1091,6 +1129,21 @@ impl<'t> Walker<'t> {
|
||||
};
|
||||
}
|
||||
|
||||
// `=> BlocProvider<CounterCubit>.value(…)` — a constructor called
|
||||
// with type arguments, as most expressions parse it. The type is the
|
||||
// LAST type_identifier: an import prefix (`p.X<T>.named`) is one too.
|
||||
if node.kind() == "constructor_invocation" {
|
||||
let mut c1 = node.walk();
|
||||
let type_id = node.named_children(&mut c1).filter(|c| c.kind() == "type_identifier").last();
|
||||
let mut c2 = node.walk();
|
||||
let name_id = node.named_children(&mut c2).find(|c| c.kind() == "identifier");
|
||||
return match (type_id, name_id) {
|
||||
(Some(t), Some(n)) => Some(format!("{}.{}", self.text(t), self.text(n))),
|
||||
(Some(t), None) => Some(self.text(t).to_string()),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1119,6 +1172,9 @@ impl<'t> Walker<'t> {
|
||||
if ap.kind() == "identifier" {
|
||||
return Some(format!("{}.{}", self.text(ap), self.text(method_id)));
|
||||
}
|
||||
if let Some(type_name) = self.type_arguments_receiver(ap) {
|
||||
return Some(format!("{}.{}", type_name, self.text(method_id)));
|
||||
}
|
||||
}
|
||||
return Some(self.text(method_id).to_string());
|
||||
}
|
||||
@@ -1126,6 +1182,23 @@ impl<'t> Walker<'t> {
|
||||
None
|
||||
}
|
||||
|
||||
/// dartTypeArgumentsReceiver (dart.ts) — `BlocProvider` in
|
||||
/// `BlocProvider<CounterCubit>.value(…)`, when `selector` is the `<…>`
|
||||
/// between the type and `.value`. Only a constructor is called that way.
|
||||
fn type_arguments_receiver(&self, selector: Node<'t>) -> Option<&'t str> {
|
||||
if selector.kind() != "selector" || selector.named_child_count() != 1 {
|
||||
return None;
|
||||
}
|
||||
if selector.named_child(0)?.kind() != "type_arguments" {
|
||||
return None;
|
||||
}
|
||||
let type_node = selector.prev_named_sibling()?;
|
||||
if type_node.kind() != "identifier" {
|
||||
return None;
|
||||
}
|
||||
Some(self.text(type_node))
|
||||
}
|
||||
|
||||
// --- extractStaticMemberRef — the dart branch (:4759-4767) ------------
|
||||
|
||||
fn extract_static_member_ref(&mut self, node: Node<'t>) {
|
||||
@@ -1326,6 +1399,20 @@ impl<'t> Walker<'t> {
|
||||
} else {
|
||||
node
|
||||
};
|
||||
// A redirecting factory names the class it constructs after `=`, then
|
||||
// perhaps that class's constructor (`= _$QuestionImpl.fromJson`, two
|
||||
// type_identifiers). Neither the constructor nor an import prefix is
|
||||
// a type, and Dart writes types UpperCamel.
|
||||
if sig.kind() == "redirecting_factory_constructor_signature" {
|
||||
let mut cursor = sig.walk();
|
||||
let kids: Vec<Node<'t>> = sig.named_children(&mut cursor).collect();
|
||||
for child in kids {
|
||||
if child.kind() != "type_identifier" || is_upper_camel(self.text(child)) {
|
||||
self.type_refs_from_subtree(child, row);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.type_refs_from_subtree(sig, row);
|
||||
}
|
||||
|
||||
@@ -1354,13 +1441,7 @@ impl<'t> Walker<'t> {
|
||||
/// constructor call names.
|
||||
fn is_type_name_dart(&self, node: Node<'t>) -> bool {
|
||||
let text = self.text(node);
|
||||
let upper = text
|
||||
.trim_start_matches(&['_', '$'][..])
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_ascii_uppercase())
|
||||
.unwrap_or(false);
|
||||
if !upper || is_builtin_type(text) {
|
||||
if !is_upper_camel(text) || is_builtin_type(text) {
|
||||
return false;
|
||||
}
|
||||
if node.next_sibling().map(|s| s.kind() == ".").unwrap_or(false) {
|
||||
|
||||
Reference in New Issue
Block a user