mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-23 19:02:52 +08:00
fix(dart): a member with no body keeps its dartdoc and annotations (#2382)
A Dart member written without a body - a constructor like `Foo._();` or
`Point.origin() : x = 0;`, a `const` constructor or redirecting factory
such as flutter_bloc's `const BlocProvider.value(...)`, an abstract or
`external` method - parses as a `declaration` wrapping its signature,
and its `///` dartdoc and `@annotation`s are siblings of that wrapper.
Both extractors looked for them before the signature, inside the
wrapper, and found nothing: the docstring was lost, and
`@visibleForTesting Foo._();` emitted no `decorates` reference.
Extraction (TS + kernel, at parity):
- A Dart-only `getDeclarationWrapper` hook returns the `declaration`
when the signature is its first named child. `docstringFor` and
extractDecoratorsFor's preceding-sibling scan start from it; the
kernel's `declaration_wrapper` does the same for every docstring
lookup and for the annotation scan.
- The shared DOCSTRING_WRAPPER_TYPES / `is_wrapper` list is untouched:
C/C++ share it, and their `declaration` wraps declarators. All 35
non-Dart parity fixtures extract byte-identically before and after.
Validated on felangel/bloc @b9be1e2 and rrousselGit/riverpod @4ba1be2
at main 3f96f80f with the kernel loaded: bloc +32 docstrings, riverpod
+126, none changed or removed, and 0 edges added or removed. The new
`decorates` refs (+10 / +186) all name SDK or package:meta annotations
and stay unresolved. Kernel parity sweeps show 0 diffs, and full-index
kernel and wasm dumps are byte-identical on both repos.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -582,6 +582,29 @@ impl<'t> Walker<'t> {
|
||||
false
|
||||
}
|
||||
|
||||
/// getDeclarationWrapper (dart.ts) — a member with no body (`Foo._();`,
|
||||
/// `const Foo.c();`, an abstract `void m();`) is a `declaration` wrapping
|
||||
/// its signature, and the member's `///` dartdoc and `@annotation`s come
|
||||
/// before the wrapper. A signature that opens the declaration takes both
|
||||
/// from there.
|
||||
fn declaration_wrapper(&self, node: Node<'t>) -> Option<Node<'t>> {
|
||||
let parent = node.parent()?;
|
||||
if parent.kind() != "declaration" {
|
||||
return None;
|
||||
}
|
||||
if parent.named_child(0)? == node {
|
||||
Some(parent)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// docstringFor (tree-sitter.ts) — the preceding comment run, looked up
|
||||
/// from the declaration wrapper when there is one.
|
||||
fn docstring_of(&self, node: Node<'t>) -> Option<String> {
|
||||
preceding_docstring(self.declaration_wrapper(node).unwrap_or(node), self.src)
|
||||
}
|
||||
|
||||
/// resolveBody (dart.ts:158-171).
|
||||
fn resolve_body(&self, node: Node<'t>) -> Option<Node<'t>> {
|
||||
if matches!(node.kind(), "function_signature" | "method_signature") {
|
||||
@@ -804,7 +827,7 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let docstring = preceding_docstring(node, self.src);
|
||||
let docstring = self.docstring_of(node);
|
||||
let signature = self.signature_of(node);
|
||||
let visibility = self.visibility_of(node);
|
||||
let is_async = self.is_async_of(node);
|
||||
@@ -851,7 +874,7 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
return;
|
||||
}
|
||||
let docstring = preceding_docstring(node, self.src);
|
||||
let docstring = self.docstring_of(node);
|
||||
let signature = self.signature_of(node);
|
||||
let visibility = self.visibility_of(node);
|
||||
let is_async = self.is_async_of(node);
|
||||
@@ -893,7 +916,7 @@ impl<'t> Walker<'t> {
|
||||
// fallback finds the ON type's type_identifier — a class named after
|
||||
// the extended type (preserved).
|
||||
let name = self.extract_name(node);
|
||||
let docstring = preceding_docstring(node, self.src);
|
||||
let docstring = self.docstring_of(node);
|
||||
let visibility = self.visibility_of(node);
|
||||
let row = self.create_node(
|
||||
"class",
|
||||
@@ -924,7 +947,7 @@ impl<'t> Walker<'t> {
|
||||
None => return,
|
||||
};
|
||||
let name = self.extract_name(node);
|
||||
let docstring = preceding_docstring(node, self.src);
|
||||
let docstring = self.docstring_of(node);
|
||||
let visibility = self.visibility_of(node);
|
||||
let row = self.create_node(
|
||||
"enum",
|
||||
@@ -966,7 +989,7 @@ impl<'t> Walker<'t> {
|
||||
if name == "<anonymous>" {
|
||||
return false;
|
||||
}
|
||||
let docstring = preceding_docstring(node, self.src);
|
||||
let docstring = self.docstring_of(node);
|
||||
// `value` field is null (type_alias has no fields) → no refs from
|
||||
// the aliased type; returns false → children re-visited.
|
||||
self.create_node("type_alias", &name, node, Extra { docstring, ..Default::default() });
|
||||
@@ -1397,8 +1420,10 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
// Scan 2: preceding siblings, backward, stop at the first
|
||||
// non-annotation — stacked annotations emit in REVERSE source order.
|
||||
if let Some(parent) = decl.parent() {
|
||||
let decl_start = decl.start_byte();
|
||||
// A `declaration`-wrapped member's annotations precede the wrapper.
|
||||
let anchor = self.declaration_wrapper(decl).unwrap_or(decl);
|
||||
if let Some(parent) = anchor.parent() {
|
||||
let decl_start = anchor.start_byte();
|
||||
let mut decl_idx: Option<usize> = None;
|
||||
for i in 0..parent.named_child_count() {
|
||||
if let Some(sib) = parent.named_child(i) {
|
||||
|
||||
Reference in New Issue
Block a user