-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
fix(dart): resolve import/export uris to file nodes (#2329) #2570
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v8
Are you sure you want to change the base?
Changes from 1 commit
66fe1ab
fb3ba1c
3cbc858
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |||||||||
|
|
||||||||||
| from pathlib import Path | ||||||||||
| from graphify.extractors.base import _file_stem, _make_id | ||||||||||
| from graphify.extractors.resolution import _resolve_dart_import_target | ||||||||||
|
|
||||||||||
|
|
||||||||||
| def extract_dart(path: Path) -> dict: | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
fans out to 7 callees (efferent coupling); 9 callers depend on it (afferent coupling). Grounded coupling-delta finding (deterministic), not an LLM guess. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
fans out to 7 callees (efferent coupling); 9 callers depend on it (afferent coupling). Grounded coupling-delta finding (deterministic), not an LLM guess. |
||||||||||
|
|
@@ -500,17 +501,21 @@ def _find_matching_brace(text: str, start_pos: int) -> int: | |||||||||
| add_edge(nid, route_nid, "navigates", context="route_object") | ||||||||||
|
|
||||||||||
| # 6. Imports and Exports | ||||||||||
| for m in re.finditer(r"""^\s*import\s+['"]([^'"]+)['"]""", src_clean, re.MULTILINE): | ||||||||||
| pkg = m.group(1) | ||||||||||
| tgt_nid = _make_id(pkg) | ||||||||||
| add_node(tgt_nid, pkg, source_file=None) | ||||||||||
| add_edge(file_nid, tgt_nid, "imports") | ||||||||||
|
|
||||||||||
| for m in re.finditer(r"""^\s*export\s+['"]([^'"]+)['"]""", src_clean, re.MULTILINE): | ||||||||||
| pkg = m.group(1) | ||||||||||
| tgt_nid = _make_id(pkg) | ||||||||||
| add_node(tgt_nid, pkg, source_file=None) | ||||||||||
| add_edge(file_nid, tgt_nid, "exports") | ||||||||||
| for kind, pattern in (("imports", r"""^\s*import\s+['"]([^'"]+)['"]"""), | ||||||||||
| ("exports", r"""^\s*export\s+['"]([^'"]+)['"]""")): | ||||||||||
| for m in re.finditer(pattern, src_clean, re.MULTILINE): | ||||||||||
| pkg = m.group(1) | ||||||||||
| # Resolve the URI so the edge target is the id that file's own node | ||||||||||
| # carries. Without this every Dart import edge pointed at a bare string | ||||||||||
| # node with source_file=None, so reverse traversal ("who imports this | ||||||||||
| # file", `affected`) was blind on Dart while Python/TS resolved (#2329). | ||||||||||
| resolved = _resolve_dart_import_target(pkg, str(path)) | ||||||||||
| if resolved is not None: | ||||||||||
| add_edge(file_nid, _make_id(str(resolved)), kind, context="import") | ||||||||||
| else: | ||||||||||
|
Comment on lines
+514
to
+515
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. export/import edge target uses resolved file path id, not the file node's own id scheme — agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review Graphify suggests a fix:
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Measured this rather than argue it, since the finding is flagged unverified — it doesn't reproduce, and I'd rather not change the id scheme on a hunch. I applied the suggestion verbatim ( Keeping
I also probed the one case where the two could genuinely diverge, since I call Happy to switch if there's a scenario I haven't constructed — I just couldn't produce one where the current form is the weaker of the two. |
||||||||||
| tgt_nid = _make_id(pkg) | ||||||||||
| add_node(tgt_nid, pkg, source_file=None) | ||||||||||
| add_edge(file_nid, tgt_nid, kind) | ||||||||||
|
|
||||||||||
| # 7. Generic Invocations / Type Lookups (Universal Dependency Lookup) | ||||||||||
| # Matches any method call with type parameters: methodName<Type>() or object.methodName<Type>() | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -565,6 +565,187 @@ def _resolve_c_include_path(raw: str, str_path: str) -> "Path | None": | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return candidate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _DART_PUBSPEC_NAME_RE = re.compile(r"""^name:\s*['"]?([A-Za-z_]\w*)['"]?\s*(?:#.*)?$""", re.MULTILINE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _DART_WORKSPACE_RE = re.compile(r"^workspace:\s*(?:#.*)?$", re.MULTILINE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _DART_PACKAGE_INDEX_CACHE: "dict[str, tuple[tuple[str, str], ...]]" = {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _DART_MAX_PARENTS = 40 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _dart_pubspec_name(pubspec: Path) -> "str | None": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Read the `name:` of a pubspec.yaml without a YAML dependency.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text = pubspec.read_text(encoding="utf-8", errors="replace") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except OSError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| m = _DART_PUBSPEC_NAME_RE.search(text) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return m.group(1) if m else None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _dart_workspace_members(pubspec: Path) -> "list[Path]": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Members listed under a pub-workspace `workspace:` block (#pub workspaces). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Only the top-level block is read; the entries are directory paths relative to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| the pubspec's own directory: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| workspace: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - apps/client_app | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - packages/pricing_catalog | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| text = pubspec.read_text(encoding="utf-8", errors="replace") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except OSError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| m = _DART_WORKSPACE_RE.search(text) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if m is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| members: list[Path] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for line in text[m.end():].splitlines(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not line.strip(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| entry = re.match(r"^\s+-\s+(?:['\"])?([^'\"#\s]+)(?:['\"])?\s*(?:#.*)?$", line) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if entry is None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break # block ended (a new top-level key, or a non-list line) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| members.append(pubspec.parent / entry.group(1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return members | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _dart_package_index(start_dir: str) -> "tuple[tuple[str, str], ...]": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Map every reachable Dart package name to its package root directory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Walks up from `start_dir` recording each `pubspec.yaml` it passes, and at each | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| one also records the members of a pub `workspace:` block, so an import of a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sibling package in a monorepo resolves as well as the file's own package. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Cached per starting directory; the result is a tuple of (name, root) pairs so | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| it stays immutable across callers. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cached = _DART_PACKAGE_INDEX_CACHE.get(start_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if cached is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return cached | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| found: dict[str, str] = {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| here = Path(start_dir).resolve() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except OSError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| here = Path(start_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for parent in [here, *here.parents][:_DART_MAX_PARENTS]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pubspec = parent / "pubspec.yaml" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not pubspec.is_file(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = _dart_pubspec_name(pubspec) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if name: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| found.setdefault(name, str(parent)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for member in _dart_workspace_members(pubspec): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| member_spec = member / "pubspec.yaml" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not member_spec.is_file(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| member_name = _dart_pubspec_name(member_spec) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if member_name: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| found.setdefault(member_name, str(member)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = tuple(sorted(found.items())) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _DART_PACKAGE_INDEX_CACHE[start_dir] = result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _dart_lib_root(start_dir: Path) -> "Path | None": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """The `lib/` of the package owning `start_dir`, if it is inside one. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| A directory only counts when its parent holds a pubspec.yaml, so a `lib/` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| belonging to something else (an asset tree, a vendored copy) is not mistaken | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for a package root. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| here = start_dir.resolve() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except OSError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for parent in [here, *here.parents][:_DART_MAX_PARENTS]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if parent.name == "lib" and (parent.parent / "pubspec.yaml").is_file(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return parent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _resolve_dart_import_target(raw: str, str_path: str) -> "Path | None": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Resolve a Dart `import`/`export`/`part` URI to a real file on disk. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dart resolution is fully deterministic — there is no extension guessing, no | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| index file, and no alias config, so only three forms exist: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'dart:async' SDK, always external -> None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '../cell/cell_widget.dart' relative to the importing file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'package:app/x/y.dart' `lib/x/y.dart` of package `app` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns the resolved path, or None when the URI names something outside the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scanned corpus (the SDK, a pub dependency, a dangling path) — mirroring | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `_resolve_c_include_path`, so the caller keeps its existing external-node | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| behaviour untouched. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not raw: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if raw.startswith("dart:"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| start_dir = Path(str_path).parent | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if raw.startswith("package:"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pkg, _, sub = raw[len("package:"):].partition("/") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. _resolve_dart_import_target() — high coupling complexity (Ca·Ce = 12) Graphify suggests a fix:
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adopted in fb3ba1c, essentially as suggested. I split the relative branch out as its own function rather than leaving it inline, because it carries the Behaviour is unchanged: same 13 tests, and the corpus in the PR description still measures 2129/2129. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not pkg or not sub: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for name, root in _dart_package_index(str(start_dir)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if name != pkg: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| candidate = (Path(root) / "lib" / sub).resolve() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except OSError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return candidate if candidate.is_file() else None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if raw.startswith(("http:", "https:", "asset:")): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # A relative URI is resolved against the importing library's OWN uri, and for | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # anything under `lib/` that uri is `package:<name>/...`, not a file path. The | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # difference is not cosmetic: RFC 3986 drops `..` segments that would escape the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # base, so from `package:app/core/data/x.dart` an import of | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # `../../../features/y.dart` CLAMPS at the package root and resolves to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # `package:app/features/y.dart`, while the same join over the file path escapes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # `lib/` and lands on a file that does not exist. Dart accepts the first — this | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # is live, compiling code in real repos — so resolving on the filesystem alone | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # reports a false miss on exactly those imports. Walk the segments against | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # `lib/` and clamp there to match. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lib_root = _dart_lib_root(start_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if lib_root is not None: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| segments = list(start_dir.resolve().relative_to(lib_root).parts) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except ValueError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| segments = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for part in raw.split("/"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if part in ("", "."): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if part == "..": | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if segments: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| segments.pop() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue # already at the package root: clamp, as `package:` does | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| segments.append(part) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| candidate = lib_root.joinpath(*segments) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return candidate if candidate.is_file() else None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Outside `lib/` (bin/, test/, tool/, a loose script) the base really is a file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # uri, so a plain join is the correct semantics. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| candidate = (start_dir / raw).resolve() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except OSError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return candidate if candidate.is_file() else None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _resolve_lua_import_target(raw_module: str, str_path: str) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Resolve a Lua require() module name to a node id. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extract_dart()fans out to 7 callees (efferent coupling); 9 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged, but I don't think there's a fix here — the new callee is the change.
extract_dartpreviously synthesized its import targets inline (_make_id(pkg)on the raw uri string), which is precisely why they never reached a file node. Resolving them means calling a resolver, so the +1 efferent edge is the feature rather than a side effect. The alternative — inlining the resolution back intoextract_dart— would keep the coupling number flat while making the function considerably larger and duplicating logic thatresolution.pyalready owns for JS/TS, C and Lua.For what it's worth, this is the same shape
extract_objcalready has: it calls_resolve_c_include_pathfrom the same module for exactly the same reason.Happy to restructure if you'd rather the call site sit somewhere else.