feat(extractor+resolver): Import Graph Linking — cross-file call resolution (#13)#48
Conversation
…lution (#13) Closes #13. ## Extractor changes (python_extractor.py) - Creates a FILE node per file with import_map in metadata - Parses import_statement (import X, import X as Y) - Parses import_from_statement (from X import Y [as Z], relative imports) - Handles relative imports (leading dots) via _resolve_relative_module() - Wildcards (from X import *) are safely skipped - Emits structural IMPORTS edges from FILE node to base module ## Resolver changes (engine.py) - _file_imports index: normalized file_path → {alias: target_fqn} - _suffix_map index: suffix → [node_ids] (O(1) src/-layout lookup) - _map_to_node_fqn(): handles two prefix-mismatch directions: 1. Import has extra prefix: cgis.resolver.X → resolver.X (strip from import) 2. Node has extra layout prefix: cgis.pipeline.X → src.cgis.pipeline.X - _resolve_global_call: import map consulted before global symbol index ## Impact - 72.8% → 58.9% unresolved edges on this project's own graph - 492 edges newly resolved via import-based linking - Top previously-unresolved internal calls (pipeline.run, MermaidCompiler.compile) now link correctly via import map Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request implements import graph extraction and linking for Python files, adding support for parsing absolute, aliased, and relative imports, as well as resolving global calls using file-level import maps. The review feedback highlights three key issues: parenthesized multi-line imports are currently ignored because the extractor does not descend into import_list nodes; non-aliased dotted imports (e.g., import a.b.c) are mapped incorrectly, leading to broken module-prefixed call resolutions; and the FQN mapping logic in the resolver should check the suffix map before stripping leading segments to avoid incorrect matches.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Refactor _process_import_from_statement into three focused helpers (_parse_relative_import, _collect_imported_symbols) to bring cyclomatic complexity from 14 to ~8. Move all inline imports to module level in test_pipeline.py and test_resolver.py. Shorten three over-length docstrings in test_python_extractor.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…map, suffix priority - Flatten `import_list` node in _collect_imported_symbols to support `from X import (A, B)` parenthesized style (#1) - Map non-aliased dotted imports to local name: `import a.b.c` → {"a": "a"} so resolver reconstructs `a.b.c.foo()` correctly, not `a.b.c.b.c.foo()` (#2) - Check _suffix_map before strip-leading-segments in _map_to_node_fqn to prevent ambiguous strip from overriding precise src/-layout match (#3) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements import graph linking to improve call graph resolution. It updates the Python extractor to parse import statements, generate FILE nodes containing an import_map, and emit IMPORTS edges. The resolver engine is enhanced to prioritize these import maps and utilize a suffix map to handle layout prefix mismatches (such as src/). Feedback focuses on two main issues: preventing a potential TypeError in the resolver if the import_map metadata is explicitly None, and robustly calculating leading dots in relative imports by using byte offsets instead of counting tree-sitter child nodes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
… dots byte-len
- Extract nested ternary in _resolve_global_call into if/elif/else — reduces
cognitive complexity from 16 to 15 and fixes SonarQube nested-conditional alert
- Guard import_map retrieval with `or {}` to handle explicit None in metadata
- Count relative-import leading dots via byte length (sub.end_byte - sub.start_byte)
instead of iterating sub.children, which can be empty for terminal leaf nodes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…complexity Moves direct-import and module-prefixed-call resolution out of _resolve_global_call into a dedicated helper, bringing _resolve_global_call's cognitive complexity from 16 to ~8 (well within SonarQube's 15 limit). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|



Summary
Implements Pass 2 of the resolver: per-file Local Name Map (LNM) via
import_mapstored in FILE node metadata.Extractor (
python_extractor.py)FILEnode per file withimport_mapin metadataimport X,import X as Y,from X import Y [as Z]from . import base,from ..core import models) via_resolve_relative_module()from X import *) are safely skipped (no crash)IMPORTSedges from FILE node to base moduleResolver (
resolver/engine.py)_file_importsindex:normalized_file_path → {alias: target_fqn}(from FILE node metadata)_suffix_mapindex:suffix → [node_ids]— O(1) lookup forsrc/-layout prefix mismatch_map_to_node_fqn(): handles both mismatch directions (import has extra prefix OR node has extra layout prefix)_resolve_global_callconsults import map before global symbol indexImpact
72.8% → 58.9% unresolved on this project's own graph — 492 newly resolved edges.
Top unresolved internal calls (
pipeline.run,MermaidCompiler.compile) now resolve correctly via import map.Remaining unresolved are builtins (
str,len,next) and instance method calls on local variables (runner.invoke,self._conn.execute) — those need Type Propagation (future issue).Test plan
import_mapin metadataimport os→{"os": "os"}import json as js→{"js": "json"}from cgis.pipeline import X→{"X": "cgis.pipeline.X"}from cgis.pipeline import X as Y→{"Y": "cgis.pipeline.X"}from typing import Dict, List→ both mappedfrom . import baserelative (1 dot) → correct FQNfrom ..core import modelsrelative (2 dots) → correct FQNfrom module import *→ no crash, IMPORTS edge emittedmod.func()module-prefixed call resolvessrc/layout normalizationCloses #13
🤖 Generated with Claude Code