Repository navigation
fix: v1.6.1 node identity, traversal depth, method values and handler recall - #278
Conversation
A same-kind, same-name declaration on one line — a getter/setter pair,
two {% render "x" %} tags — hashed to the node id of the first and
silently replaced it in the store. Within one file's extraction the first
declaration now keeps its id and every later one at a different column
appends :{column}, its zero-based UTF-16 column, as upstream v1.6.1 does
(#1349); revisiting a declaration yields the same id, so a declaration
that collides with nothing keeps exactly the id it had. Applied to the
tree-sitter walker (CFML included) and the Liquid extractor; the formula
in AGENTS.md and docs/equivalence.md records the suffix. Extraction
version 15.
…ted walks callers, callees and impact are depth-limited DFS walks. A node first expanded through a longer path was never expanded again when a nearer path reached it, so its own dependents within the limit were lost to edge order — impact at depth 2 already, callers and callees from depth 3. The walks now record the shallowest depth each node was expanded at and expand it again from a nearer depth (upstream #1974). Each (node, edge kind) is reported once per walk, keeping multi-kind rows while no longer repeating a node once per parent, and impact records a node's edges on its first expansion only.
pool.submit(self.store.fetch) and Submit(c.store.Fetch) pass a method as a value. The extractor now keeps the whole receiver path (self.store.fetch, c.store.Fetch, Actual.fetch) instead of a last segment or a this. marker, and resolution follows upstream v1.6.1 #1820: self/cls resolve on the enclosing class and its bases, a field on its annotated, constructed or parameter-assigned type, a local on its annotation or constructor, an imported class through the import, a Go receiver on its declared type or a struct field's in-module type, and a method expression on its type; a base-typed field reaches only that base's descendants. Otherwise a method name binds only when it is unique across the project — test doubles and abstract bases count. Data attributes, @Property members, ambiguity and unknowable receivers stay unlinked, and the capitalization heuristic is no longer used for method values.
…dler values
const UploadApi = { uploadARCapture, makeFolder: createFolder };
export default UploadApi made every UploadApi.member() call from another
file land on a guessed first exported function, because the constant is
not exported at its declaration. A default import now finds the binding the
export default statement names — a namespace object included — after a
component file and before the first-exported-function guess, and the
member resolves through the literal's own bindings.
TS/JS function values also cover JSX attribute expressions
(onPress={handleSubmit}) and object shorthand members (a hook's
return { handleApprove }), so the handler a component or hook hands out has
an edge (upstream F13, 873f133).
callers and callees now follow instantiates edges, as upstream has since #774/#804: a function that constructs a class is that class's caller, and the class is its callee. Each node in the callers/callees JSON — per definition and in the union list — carries relationships, the distinct edge kinds that link it to the queried symbol, and text output marks a non-call link as [instantiates] or [references] (upstream #1839). Three structural MCP fixtures move with the instantiation edge and nothing else: runDemo's callees gain Counter, Counter's trail and blast radius gain runDemo, and explore's symbol count and file-header order follow.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 78c4e02a06
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // An unknown receiver keeps the unique-or-drop discipline across ALL | ||
| // files; test doubles and abstract bases are candidates too. | ||
| unique_callable( | ||
| context.get_nodes_by_name_shared(member), | ||
| reference, | ||
| context, | ||
| 0.8, |
There was a problem hiding this comment.
Refuse unique-name fallback for unknown receivers
When a Python or Go receiver has no inferred type, this fallback resolves obj.fetch to the sole project method named fetch, even though there is no evidence that obj has that type. For example, def register(obj, pool): pool.submit(obj.fetch) creates a false reference to an unrelated Store.fetch whenever it is the only such method; the previous receiver-overlap path rejected this shape. Return None when receiver inference fails rather than treating global name uniqueness as receiver evidence.
AGENTS.md reference: AGENTS.md:L59-L61
Useful? React with 👍 / 👎.
| #[derive(Debug, Default)] | ||
| pub struct NodeIdAllocator { | ||
| first_columns: HashMap<String, u32>, | ||
| } |
There was a problem hiding this comment.
Apply the collision allocator to custom extractors
The allocator is wired into the generic tree-sitter walker and Liquid only, while custom extractors such as Vue and MyBatis still call generate_node_id/default_node directly. Thus a valid Vue script containing function x(){} function x(){} on one line still emits two identical IDs and one declaration is overwritten, despite extraction version 15 advertising the new collision behavior. Route every per-file declaration path through a shared allocator.
AGENTS.md reference: AGENTS.md:L46-L52
Useful? React with 👍 / 👎.
| let shadow = Regex::new(&format!( | ||
| r"(?m)^\s*(?:(?:self|cls)\.)?{}\s*(?:=|:)", | ||
| regex::escape(member) | ||
| )); | ||
| if shadow.is_ok_and(|pattern| pattern.is_match(&body)) { |
There was a problem hiding this comment.
Ignore bare locals when detecting member shadowing
Because the self|cls prefix is optional and the regex scans the entire class body, an unrelated local such as fetch = build_value() inside any method is treated as a class/instance assignment. Consequently, otherwise valid callbacks such as pool.submit(self.fetch) are left unresolved even when the class defines def fetch(...). Bare assignments should count only at class scope; assignments inside methods should require an explicit self. or cls. receiver.
Useful? React with 👍 / 👎.
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (92.50%) is below the target coverage (95.00%). You can increase the patch coverage or adjust the target coverage. @@ Coverage Diff @@
## main #278 +/- ##
==========================================
- Coverage 95.20% 95.17% -0.03%
==========================================
Files 153 154 +1
Lines 92926 93662 +736
==========================================
+ Hits 88472 89147 +675
- Misses 4454 4515 +61
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
Summary
Fourth PR of the v1.6.1 alignment, following #277. It lands the owner-approved node-identity change, the traversal and method-value fixes, and the remaining extraction recall rows. Dispositions and landing state are tracked in
docs/upstream-sync/V1_6_1_AUDIT_2026-09-30.md; tracked parity inUPSTREAM.mdstaysv1.6.0until the release gate.Same-line node identity (#1349) — extraction version 14 → 15
A same-kind, same-name declaration on one line (a getter/setter pair, two
{% render "x" %}tags) hashed to the first one's node id and silently replaced it. Within one file's extraction the first keeps its id and every later one at a different column appends:{column}in UTF-16 code units, as upstream'sNodeIdAllocatordoes; a declaration that collides with nothing keeps exactly the id it had. The owner approved this change to the node-id invariant on 2026-10-01;AGENTS.mdanddocs/equivalence.mdrecord the suffix.codegraph node <suffixed-id>resolves the later declaration.Traversal (#1974)
callers,calleesandimpactre-expand a node reached nearer than before, so dependents within the depth limit are no longer lost to edge order (impact from depth 2, callers/callees from depth 3). Each(node, edge kind)is reported once per walk.Method values (#1820)
Python and Go member values keep their whole receiver path (
self.store.fetch,c.store.Fetch,Actual.fetch) and resolve through the receiver's scope — the enclosing class and its bases, a field's annotated/constructed/parameter type, a local's type, an import, a Go struct field's in-module type, a method expression — before a project-unique name. Data attributes,@propertymembers, test doubles, abstract bases and unknowable receivers stay unlinked; the capitalization heuristic no longer guesses method values.Recall
const Api = { upload }; export default Api— a default import finds the binding theexport defaultstatement names, andApi.upload()resolves through the literal's own bindings.onPress={handleSubmit}) and object shorthand members (a hook'sreturn { handleApprove }) are function values.instantiatesedges as upstream has since June; each node in the JSON carriesrelationships, and text output marks[instantiates]/[references].Golden impact
instantiatesedge and nothing else —runDemo's callees gainCounter,Counter's trail and blast radius gainrunDemo, and explore's symbol count and file-header order follow.Verification
-D warnings, full test suite, guardrail) passed on the pushed head;docs-check,oxfmt --checkandscripts/guardrail.shclean.oxfmt --checkanddocs-check.node_identity, the same-line accessor persistence case, the #1974 traversal cases,method_values, the F13 extraction/resolution cases, and the CLI relationships case.this.x→self.x, v1.6.1 #1820) in two unit tests and one coverage test, andcallees(runDemo)now including the instantiatedCounter(upstream #774/#804).BEGIN_COMMIT_OVERRIDE
fix(extract): keep same-line declarations that share a node id
fix(graph): re-expand a node reached nearer than before in depth-limited walks
fix(resolve): scope Python and Go method values by their receiver
feat(resolve): resolve default-exported namespace objects and JSX handler values
feat(cli): report how each caller or callee relates to the symbol
END_COMMIT_OVERRIDE
🤖 Generated with Claude Code