fix(dart): a library links the files its part directives name (#2391)

A Dart `part 'x.g.dart';` now mints an `import` node (name = the URI,
signature = the directive) and an `imports` ref from the library's file
node, in both extractors. The ref resolves through `dartDirectiveFile` by
the rules an import or export follows, so the library's file links the
part's file. A library and its parts are one library, so a change to a
part, generated or hand-written, now reaches the library and what imports
it in `codegraph affected` and the files listed as using the part. Before,
neither `part` nor `part of` left a trace, and the two files were joined
only by whatever symbol edges happened to cross between them: riverpod's
framework.dart, which holds nothing but `part` lines, by none.

`part of` still records nothing: the library's edge already joins the two
files, and a part -> library edge would list every pair on both import
rails of both files and make each a file 2-cycle.

The kernel's directive arm now goes on into the directive's children, as
the TS ladder does. Its `import_or_export` arm used to return first, so an
annotated directive's arguments (`@Tag(f) import 'a.dart';`) were fn-ref
candidates on the wasm path only.

Validation on rrousselGit/riverpod @4ba1be2 and felangel/bloc @b9be1e2
with the kernel loaded, base 837a1866:
- bloc: +53 import nodes, +53 contains, +53 imports (library -> part).
- riverpod: +294 import nodes, +294 contains, +258 imports, +36 refs left
  unresolved (generated parts that are not committed).
- Nothing removed; kernel and wasm dumps byte-identical; parity sweeps
  0 diffs. Every added edge sits on a `part` line whose URI names the
  target, and the target's `part of` names the library back (311/311).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-06 19:16:55 +00:00
committed by GitHub
parent 837a1866f1
commit 83f54a1853
10 changed files with 353 additions and 49 deletions
+13 -3
View File
@@ -826,9 +826,13 @@ impl<'t> Walker<'t> {
return;
}
}
"import_or_export" => {
// importTypes (dart.ts): `import` / `export`, and `part
// 'x.g.dart';`, which links the library to the part's file. The
// TS ladder goes on into the directive's children, so this does
// too: an annotation's arguments are fn-ref capture points
// (`@Tag(bar) import 'a.dart';` captures `bar`).
"import_or_export" | "part_directive" => {
self.extract_import(node);
return;
}
"new_expression" => {
// INSTANTIATION_KINDS row — from the FILE/CLASS on the
@@ -1036,7 +1040,7 @@ impl<'t> Walker<'t> {
false
}
// --- extractImport (:3170; hook dart.ts:261-304) ----------------------
// --- extractImport (:3170; hook dart.ts extractImport) -----------------
fn extract_import(&mut self, node: Node<'t>) {
let find_child = |parent: Node<'t>, kind: &str| -> Option<Node<'t>> {
@@ -1050,6 +1054,12 @@ impl<'t> Walker<'t> {
find_child(uri, "string_literal")
};
let mut module: Option<String> = None;
// A part: `part 'x.g.dart';` holds its `uri` directly.
if node.kind() == "part_directive" {
if let Some(sl) = find_child(node, "uri").and_then(|uri| find_child(uri, "string_literal")) {
module = Some(self.text(sl).replace(['\'', '"'], ""));
}
}
if let Some(li) = find_child(node, "library_import") {
if let Some(spec) = find_child(li, "import_specification") {
if let Some(sl) = uri_of(spec) {