Skip to content

feat(ingestion): Link object literal methods to exported bindings#1718

Merged
magyargergo merged 18 commits into
abhigyanpatwari:mainfrom
luyua9:fix/ts-object-method-exports-luyua9
May 21, 2026
Merged

feat(ingestion): Link object literal methods to exported bindings#1718
magyargergo merged 18 commits into
abhigyanpatwari:mainfrom
luyua9:fix/ts-object-method-exports-luyua9

Conversation

@luyua9

@luyua9 luyua9 commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #1358.

TypeScript/JavaScript object literal shorthand methods such as:

export const fooService = {
  async getUser(id: string) {
    return findUser(id);
  },
};

were parsed as top-level Method nodes with only DEFINES edges from the file. The exported object binding (fooService) had no edge to those methods, so graph/impact queries could not discover fooService.getUser.

This PR adds a narrow AST helper for object-literal method owners and uses it in both parsing paths to emit HAS_METHOD from the exported object binding to shorthand object methods.

Validation

  • npx vitest run test/unit/parsing-worker-fallback.test.ts
  • git diff --check

@vercel

vercel Bot commented May 20, 2026

Copy link
Copy Markdown

@luyua9 is attempting to deploy a commit to the NexusCore Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 6m 44s —— View job


PR #1718 Production-Readiness Review

Reviewer: Adversarial production-readiness review — ingestion, AST parsing, graph relationships, scope-resolution migration safety.

Task List

  • Read repo docs (AGENTS.md, GUARDRAILS.md, CONTRIBUTING.md, ARCHITECTURE.md, TESTING.md, DoD.md)
  • Determine TS/JS migration ownership (legacy DAG vs scope-resolution)
  • Inspect all 4 changed files line-by-line
  • Inspect related language provider and scope-resolution files
  • Check ID generation, label logic, and symbol table consistency
  • Verify caller/impact resolution path
  • Check sequential vs worker path parity
  • Run hygiene/Unicode checks
  • Verify CI status
  • Formulate findings and final verdict

Review Bar

The bar is: every item in §§ 2.1–2.7 of DoD.md relevant to this change, plus the 12-item PR-specific DoD from the task briefing. The weakest production-critical lane determines the verdict.


Current PR State

One commit (9a27e6c), 4 changed TypeScript files, +122/−8. Clean branch — no unrelated merge churn, no formatting noise, no generated artifacts.

Branch hygiene: clean feature/fix PR.


Understanding of the Change

The PR adds a new helper findObjectLiteralBindingInfo in ast-helpers.ts that walks the AST up from a method_definition inside an object node, finds the enclosing variable_declarator, and returns the owner's graph node ID. Both the sequential path (parsing-processor.ts) and the worker path (parse-worker.ts) are extended to call this helper when a Method node has no enclosing class, and to emit a HAS_METHOD relationship from the owning exported Const/Variable binding to the method. One test verifies the happy path in sequential fallback mode.


Architecture / Migration Ownership Finding

TypeScript and JavaScript are both migrated to scope-resolution (RFC #909). Both providers implement emitScopeCaptures (typescript.ts:316, javascript would be :379 per search results). A scope-extractor-bridge.ts and full scope-resolver directories exist for both.

However: the scope-resolution path is explicitly documented as additive to, not a replacement of, the legacy DAG. scope-extractor-bridge.ts:24 states: "Scope-based resolution is the new path under construction — it must not destabilize the legacy DAG. Legacy parsing on the same file continues unaffected." The WorkerExtractedData interface in parsing-processor.ts:83–84 confirms: "parsedFiles are additive to the legacy DAG path."

The structural edges (DEFINES, HAS_METHOD, HAS_PROPERTY) are exclusively built by the legacy DAG. The scope-resolution path produces ParsedFile artifacts for CALLS resolution. The fix to the legacy DAG for structural HAS_METHOD edges is therefore correct placement — this is not a migration boundary violation.


Files Inspected

  • gitnexus/src/core/ingestion/utils/ast-helpers.ts (full)
  • gitnexus/src/core/ingestion/parsing-processor.ts (full)
  • gitnexus/src/core/ingestion/workers/parse-worker.ts (partial: imports + ~lines 2050–2370)
  • gitnexus/test/unit/parsing-worker-fallback.test.ts (full)
  • gitnexus/src/core/ingestion/scope-extractor-bridge.ts (full)
  • gitnexus/src/core/ingestion/scope-extractor.ts (header)
  • gitnexus/src/core/ingestion/languages/typescript.ts (partial)
  • gitnexus/src/core/ingestion/languages/typescript/scope-resolver.ts (full)
  • gitnexus/src/core/ingestion/scope-resolution/scope/walkers.ts (lines 358–431)
  • gitnexus/src/core/ingestion/scope-resolution/passes/receiver-bound-calls.ts (partial)
  • gitnexus/src/core/ingestion/call-processor.ts (partial)
  • gitnexus/src/core/ingestion/tree-sitter-queries.ts (relevant snippets)
  • DoD.md, ARCHITECTURE.md (full)

CI / Checks Status

Of 9 workflow runs, 8 are action_required (require maintainer authorization to run for a fork PR) and 1 is success (conventional labeler). The required gates — CI (typecheck, unit tests, lint), CodeQL, Gitleaks, Dependency Review — have not executed against this commit. This is not the PR author's fault (fork auth gate), but it means zero CI validation has been confirmed.


Findings


Finding 1 — BLOCKER: Caller/Impact Resolution Remains Broken (Lane 3)

Severity: CRITICAL
File: parsing-processor.ts:785–795, parse-worker.ts:2321
Evidence:

// parsing-processor.ts:793
symbolTable.add(file.path, nodeName, nodeId, nodeLabel, {
  ...
  ownerId: enclosingClassId ?? undefined,   // ← object literal ownerId is NOT set
  ...
});

// parse-worker.ts:2321
...(enclosingClassId ? { ownerId: enclosingClassId } : {}),  // ← same gap

When an object literal method is parsed, enclosingClassId is null (no class enclosure — that's the problem). The PR adds a HAS_METHOD graph edge from the exported binding to the method, but it does not set ownerId for the method in the symbol table. The legacy call-processor (call-processor.ts) resolves fooService.getUser(id) via receiverTypeName + owner-indexed symbol table lookup. Without an ownerId, getUser is not in the owner index, and receiverTypeName for a lowercase-receiver object literal binding defaults to undefined (the capital-initial heuristic at call-processor.ts:1160 doesn't fire for fooService). The scope-resolution path (receiver-bound-calls.ts) also uses model.methods.lookupAllByOwner(ownerId, ...) — same gap.

Production risk: Issue #1358's core symptom — gitnexus_impact reporting false LOW risk for calls like credentialService.verifyPassword(...) — is not fixed. The structural HAS_METHOD edge exists in the graph but CALLS edges from callers are still not created, leaving impact traversal blind to real callers.

Recommended fix: Also set ownerId to objectLiteralOwnerInfo.ownerId in the symbol table for methods resolved via the object literal path. Additionally, extend the receiver-type resolution chain so that when a binding resolves to an exported object (Const/Variable node with type 'object-literal' or equivalent), member calls against it can be indexed and traversed.

Blocks merge: YES.


Finding 2 — BLOCKER: Boundary Guard Is Ineffective for Local Object Literals (Lane 6)

Severity: HIGH
File: ast-helpers.ts:432–466
Evidence:

while (current) {
  if (current.type === 'object') sawObjectLiteral = true;

  if (sawObjectLiteral && current.type === 'variable_declarator') {
    // ← returns here for the local case
    const nameNode = current.childForFieldName?.('name');
    ...
    return { ownerId: ..., ownerName: ... };
  }

  // ← boundary check never reached for local case
  if (current !== node && (FUNCTION_NODE_TYPES... || CLASS_CONTAINER_TYPES...)) {
    return null;
  }
  current = current.parent;
}

For a local object literal like:

export function processAll() {
  const handler = { async run(x: string) { return fetch(x); } };
}

The AST parent walk from run is: method_definitionobjectvariable_declarator (for handler). The variable_declarator is found at step 3. The enclosing function_declaration boundary sits at step 5 (lexical_declarationstatement_blockfunction_declaration) and is never reached. The tree-sitter query captures all lexical_declaration nodes regardless of scope depth, so Const:foo.ts:handler already exists in the graph. Result: handler.HAS_METHOD.run is emitted for a local, non-exported binding — a false positive.

The same false attribution fires for:

  • Object literals inside class constructors / methods
  • Nested object literals: export const s = { nested: { method() {} } }method gets linked directly to s, skipping nested
  • Objects returned from factory functions

Production risk: Incorrect HAS_METHOD edges inflate the apparent API surface of local bindings and misattribute nested object methods to their top-level ancestor. Impact queries for s.method would erroneously include it in the blast radius of s.

Recommended fix: After finding the variable_declarator, walk its ancestor chain and return null if any FUNCTION_NODE_TYPES or CLASS_CONTAINER_TYPES ancestor appears before the root (or a top-level export_statement / program). Alternatively, restructure the loop so the boundary check always precedes the variable_declarator check (with special-casing for the starting node).

Blocks merge: YES.


Finding 3 — BLOCKER: Tests Do Not Cover the Issue's Production Symptom (Lane 5)

Severity: HIGH
File: test/unit/parsing-worker-fallback.test.ts:46–88
Evidence:

The new test asserts:

  • fooService node exists as Const
  • getUser and saveUser are Method nodes ✓
  • HAS_METHOD edges exist from fooService to both methods ✓

Missing assertions:

  • No test for a consumer file (import { fooService } from ...; fooService.getUser(id)) with a CALLS edge reaching the method.
  • No test for impact({target: "getUser", direction: "upstream"}) returning callers.
  • No negative test: a const handler = { run() {} } inside a function body should NOT emit HAS_METHOD (proves the boundary guard works).
  • No worker-mode test: the test calls processParsing without a workerPool argument, exercising only sequential fallback. Worker mode (the production code path for repos above the dispatch threshold) is unverified.

Per DoD.md §2.7: "Tests cover the real changed path — they would fail if behavior, wiring, or contracts were broken." A green test on the structural edge alone does not prove the production symptom (false LOW risk from impact) is fixed.

Blocks merge: YES (directly tied to Finding 1 — the CALLS gap cannot be verified without an end-to-end test).


Finding 4 — MEDIUM: ownerName Field Is Dead Code

Severity: LOW
File: ast-helpers.ts:417–418, ast-helpers.ts:450
Evidence:

export interface ObjectLiteralBindingInfo {
  ownerId: string;
  ownerName: string;   // ← declared
}
// ...
return {
  ownerId: generateId(ownerLabel, `${filePath}:${nameNode.text}`),
  ownerName: nameNode.text,   // ← set
};

Neither call site in parsing-processor.ts nor parse-worker.ts reads ownerName:

const ownerIdForMemberEdge = enclosingClassId ?? objectLiteralOwnerInfo?.ownerId ?? null;
// objectLiteralOwnerInfo.ownerName is never accessed

DoD.md §2.3 prohibits dead code. This is a minor cleanup but violates the "smallest correct solution" rule.

Recommended fix: Remove the ownerName field from the interface and return value.

Blocks merge: NO (minor cleanup; can be addressed in the same PR or follow-up).


Finding 5 — MEDIUM: Owner Label Heuristic Inconsistency for let

Severity: LOW
File: ast-helpers.ts:447
Evidence:

const ownerLabel = declaration?.type === 'variable_declaration' ? 'Variable' : 'Const';

var declarations use variable_declarationVariable. Both const and let use lexical_declaration → heuristic returns Const. This is internally consistent with the tree-sitter query ((lexical_declaration ...) @definition.const captures both). However, let fooService = { ... } would generate Const:file.ts:fooService both from the query capture and from this heuristic, which is semantically confusing (the variable is mutable). This is pre-existing behavior in the query design, not introduced by this PR.

Blocks merge: NO (pre-existing; not a new regression).


Finding 6 — CI Not Verified

Severity: HIGH
Evidence: 8 of 9 workflows are action_required (fork authorization gate). The main CI run (typecheck, unit tests, lint), CodeQL, Gitleaks, and Dependency Review have not run. The PR's own validation instructions (npx vitest run test/unit/parsing-worker-fallback.test.ts) cannot be confirmed from CI. A maintainer must authorize the CI runs before this can be considered for merge.

Blocks merge: YES (conditional — CI must be run and pass).


Hidden Unicode / Hygiene

  • No bidirectional control characters found in changed files (grep clean).
  • Non-ASCII characters in ast-helpers.ts are pre-existing em-dashes and arrows in comments (, ). These are ordinary punctuation, not executable code. Clean.
  • git diff --check not executable in this environment, but the branch appears whitespace-clean.

PR-Specific Assessment Sections

Migration Boundary

Pass with caveat. Both TS and JS have emitScopeCaptures, making them scope-resolution-migrated. However, the legacy DAG path remains the sole producer of structural graph edges (DEFINES, HAS_METHOD, HAS_PROPERTY). The fix correctly targets the legacy DAG structural path. The risk is that scope-resolution CALLS paths also need fixing (owner indexing via ownerId) — which this PR omits.

Object Owner Edge Correctness

Partial. For the straightforward export const fooService = { method() {} } case, the ID (Const:file.ts:fooService) matches the tree-sitter query's @definition.const label derivation. The ID is stable and correct for exported file-scope bindings. Fails for local bindings inside functions and for nested object literals (Finding 2).

Caller / Impact Correctness

Fails. The central issue — callers cannot be discovered via impact({target: "getUser", direction: "upstream"}) — is not fixed. ownerId is absent from the symbol table for object-literal methods, so the receiver-type call resolution chain cannot link fooService.getUser(id) to getUser (Finding 1).

Sequential vs Worker Path Parity

Structural parity achieved. Both parsing-processor.ts and parse-worker.ts are identically extended. However, only the sequential path is tested. The worker path is not covered by any test in this PR.

Test Adequacy

Insufficient for the stated DoD. Covers exactly one happy-path structural assertion in sequential mode. Missing: CALLS integration, impact traversal, worker mode, negative case (local binding), nested object literal, consumer file. DoD.md §2.7 requires tests that would fail if wiring or contracts broke — the current test would not catch the CALLS gap or the boundary guard bug.

Hidden Unicode / Hygiene

Clean. No bidirectional controls. Pre-existing em-dashes in comments are benign.

CI / Security

Unverified. All required CI checks are pending fork authorization. Cannot confirm typecheck, lint, or test results. Vercel deployment warning (authorization required for fork) is not a merge blocker per project policy.


Final Verdict

Not production-ready.

The PR correctly identifies the problem and places the structural fix in the right code layer (legacy DAG for HAS_METHOD edges). However, it only solves half of the issue: the CALLS resolution gap that causes false LOW impact scores remains open (Finding 1). The ownerId is not propagated to the symbol table for object-literal methods, so receiver-bound call-processor and scope-resolution passes cannot connect fooService.getUser(id) call sites to the getUser node. Additionally, the boundary guard in findObjectLiteralBindingInfo is ineffective (Finding 2) — local object literals inside functions would generate incorrect HAS_METHOD edges, creating false-positive graph connectivity. The test suite does not verify the production symptom and has no negative or worker-mode coverage (Finding 3). The weakest lanes are Findings 1 and 2 (caller resolution and nested scope correctness), which must be resolved before this PR can close issue #1358 in production.

@magyargergo

Copy link
Copy Markdown
Collaborator

@luyua9 can you please look into claude's findings? 🙏

@magyargergo

Copy link
Copy Markdown
Collaborator

@luyua9 any update?

…tion (PR abhigyanpatwari#1718 review)

Addresses adversarial production-readiness review on PR abhigyanpatwari#1718 / issue abhigyanpatwari#1358:
- F1 (caller resolution) — setting `ownerId` on object-literal method symbols
  alone is not sufficient; the scope-resolution receiver-bound resolver only
  consults class-like or type-annotated bindings, so lowercase value receivers
  (`export const fooService = {...}; fooService.getUser(...)`) never reach the
  owner-indexed lookup. Adds a Case 5 value-receiver bridge in
  receiver-bound-calls.ts that resolves the receiver name as a Const/Variable
  binding, translates its def to the canonical graph node id, and emits the
  CALLS edge via the owner-indexed method registry.
- F2 (boundary guard) — rewrites findObjectLiteralBindingInfo as an explicit
  two-phase AST walk: Phase A tracks object-literal depth (returns null for
  nested literals and pre-declarator function/class boundaries — IIFE
  patterns); Phase B walks the declarator's ancestors and rejects function,
  class, and block-statement containers (if / for / while / try / catch /
  switch / etc.) before reaching program/export_statement. Prevents false
  HAS_METHOD edges for locally-scoped or block-scoped object literals.
- F4 — drops the dead `ownerName` field from ObjectLiteralBindingInfo.

Constraint: TS/JS are scope-resolution migrated per RFC abhigyanpatwari#909; the legacy
Call-Resolution DAG (call-processor.ts) is intentionally left untouched.

Tests:
- test/integration/ast-helpers-object-literal-binding.test.ts (13 cases) —
  pins helper semantics: happy paths, function/arrow/class-ctor boundaries,
  nested literals, block scope (if / for-of / try), IIFE, assignment
  expressions without declarator.
- test/integration/object-literal-owner-resolution.test.ts (9 cases) —
  drives the full pipeline against an on-disk fixture: sequential CALLS edge
  emission (issue abhigyanpatwari#1358 proof), worker-mode parity, negative local binding,
  and nested-literal attribution boundary.

Full sweep: 2958/2958 integration + 6056/6056 unit tests pass.
@magyargergo

Copy link
Copy Markdown
Collaborator

Addressed Claude's adversarial production-readiness review in e9531cee.

Findings closure

F1 — Caller / impact resolution (BLOCKER → fixed). The review's recommended fix (setting ownerId on the symbol-table entry) was necessary but not sufficient: scope-resolution's receiver-bound-calls.ts only consults class-like or type-annotated bindings, so a lowercase value receiver like fooService with no type annotation never reaches owner-indexed lookup even when ownerId is correct.

Fix is in two parts:

  • parsing-processor.ts:793 + parse-worker.ts:2321 — symbol-table ownerId now falls back to objectLiteralOwnerInfo?.ownerId (the original review recommendation).
  • receiver-bound-calls.ts — new Case 5 (value-receiver bridge) after Case 4: resolves the receiver name via a new findValueBindingInScope walker, translates the scope-resolution Variable def to the canonical graph node id, and emits the CALLS edge against the owner-indexed method registry. Const was also added to isLinkableLabel in node-lookup.ts so the graph-bridge sees these nodes during lookup table construction.

Implemented in the scope-resolution path only — the legacy DAG (call-processor.ts) was intentionally not touched: TS/JS are scope-resolution migrated per RFC #909, so the bug fix lives where the production resolution flows.

F2 — Boundary guard (BLOCKER → fixed). findObjectLiteralBindingInfo rewritten as an explicit two-phase walk:

  • Phase A tracks objectDepth; returns null for nested literals (objectDepth > 1) and for function/class containers reached before the owning variable_declarator (catches IIFE patterns).
  • Phase B walks the declarator's own ancestors and returns null on any function, class, or block-statement container (statement_block, if_statement, for_*, while_*, try_statement, catch_clause, switch_*, with_statement) before reaching program / export_statement. This closes the additional block-scope leak surfaced during deepening (if (cond) { const handler = {...} } at file scope no longer emits a false-positive HAS_METHOD).

F3 — Test coverage (BLOCKER → fixed). Two new integration test files:

  • test/integration/ast-helpers-object-literal-binding.test.ts (13 cases) — pins helper semantics against the real tree-sitter TypeScript parser: happy paths (export const, bare const, var), function/arrow/class-constructor boundaries, nested literals, block scope (if / for-of / try), IIFE wrapping, and assignment without variable_declarator (no throw).
  • test/integration/object-literal-owner-resolution.test.ts (9 cases) — drives runPipelineFromRepo against an on-disk fixture repo (matches the codebase's existing integration pattern from cross-file-binding.test.ts):
    • Test A: sequential pipeline emits Const:fooServiceHAS_METHODMethod:getUser AND Function:callerCALLSMethod:getUser. This is the directly testable equivalent of issue TypeScript: export const fooService = { async method() {} } produces zero edges (no HAS_PROPERTY, no incoming CALLS) #1358's production symptom — impact upstream traversal is a graph walk over CALLS, so the edge's existence is the impact-API proof.
    • Test B: worker-mode pipeline (real createWorkerPool via workerThresholdsForTest) emits identical HAS_METHOD and CALLS edge sets to sequential. Gated on hasDistWorker.
    • Test C: local-scoped object literal inside a function body emits zero false-positive HAS_METHOD edges and no ownerId on the method node.
    • Test D: nested object literal binds only the top-level method to its outer Const; the nested method is not attributed (safe under-approximation, preserves pre-feat(ingestion): Link object literal methods to exported bindings #1718 behavior for that edge case).

All assertions use exact .toBe(N) / .toEqual([...]) per the project's testing conventions — no toBeGreaterThanOrEqual, no .toContain shortcuts.

F4 — Dead code (fixed). ObjectLiteralBindingInfo.ownerName removed; both call sites only read ownerId.

F5 — letConst label (pre-existing, deferred). Acknowledged in the helper's docstring; preserved as-is. Tree-sitter lexical_declaration capture design predates this PR; changing it requires a separate audit of downstream label consumers.

F6 — CI auth gate. This commit pushes from a fork, so the same fork-PR authorization gate still applies. Maintainer can authorize the workflow runs to verify on CI.

Local verification

  • tsc --noEmit: clean.
  • npm run build: produces fresh dist/parse-worker.js (needed by worker-parity test B).
  • vitest run test/integration/: 2958 passed, 13 skipped (skips are pre-existing platform/CI-specific gates).
  • vitest run test/unit/: 6056 passed, 30 skipped.
  • Targeted regression sweep on has-method.test.ts + cross-file-binding.test.ts: 100/100 pass.

Files changed

src/core/ingestion/parsing-processor.ts
src/core/ingestion/scope-resolution/graph-bridge/node-lookup.ts
src/core/ingestion/scope-resolution/passes/receiver-bound-calls.ts
src/core/ingestion/scope-resolution/scope/walkers.ts
src/core/ingestion/utils/ast-helpers.ts
src/core/ingestion/workers/parse-worker.ts
test/integration/ast-helpers-object-literal-binding.test.ts (new)
test/integration/object-literal-owner-resolution.test.ts   (new)

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

CI Report

All checks passed

Pipeline Status

Stage Status Details
✅ Typecheck success tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
9451 9450 0 1 416s

✅ All 9450 tests passed

1 test(s) skipped — expand for details
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 78.15% 31116/39811 N/A% 🟢 ███████████████░░░░░
Branches 66.69% 19817/29711 N/A% 🟢 █████████████░░░░░░░
Functions 82.19% 3171/3858 N/A% 🟢 ████████████████░░░░
Lines 81.52% 28068/34428 N/A% 🟢 ████████████████░░░░

📋 View full run · Generated by CI

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 13m 4s —— View job


PR #1718 Adversarial Production-Readiness Review

Reviewer: Adversarial production-readiness review — ingestion, AST parsing, graph relationships, scope-resolution, worker parity, false-positive suppression.

Task List

  • Post initial comment
  • Read repo docs (DoD.md, AGENTS.md, GUARDRAILS.md, CONTRIBUTING.md)
  • Verify branch hygiene (git log, merge commit inspection)
  • Inspect all 9 changed files line-by-line
  • Inspect related files (tree-sitter-queries, scope-extractor, language providers, graph-bridge ids/edges, walkers, symbol-table, method-registry, semantic-model)
  • Search for key terms (findObjectLiteralBindingInfo, ownerId, HAS_METHOD, findValueBindingInScope, tryEmitEdge, isLinkableLabel, etc.)
  • Verify CI status
  • Run Unicode/hidden-character hygiene checks
  • Assess owner ID label consistency (const/let/var)
  • Assess worker path parity
  • Assess scope-resolution value-receiver bridge (false positives, direct edge emission)
  • Assess issue TypeScript: export const fooService = { async method() {} } produces zero edges (no HAS_PROPERTY, no incoming CALLS) #1358 scope (class-instance pattern, ACCESSES edges)
  • Formulate findings and final verdict

Review Bar for This PR

DoD.md §§2.1–2.7 (correctness, placement, design, contracts, security, performance, tests). PR-specific bar from the trigger: all 12 items — owner discovered only for importable file-scope exported bindings; ownerId in both paths; HAS_METHOD in both paths; scope-resolution CALLS resolution for imported receivers; end-to-end CALLS edge as issue #1358 proof; negative cases for local/nested/block-scoped/IIFE patterns; TS/JS parity; existing resolution not regressed; direct edge emission preserves graph bridge invariants; CI passes; branch hygiene acceptable; no unrelated area touched.


Problem Being Solved

Issue #1358: export const fooService = { async getUser(id) {} } produced Method:getUser with no incoming CALLS edges and no ownership relationship, causing gitnexus_impact to return false LOW risk even for production chokepoints with live call sites. Both the structural (HAS_METHOD) and caller-resolution (CALLS) gaps needed fixing.


Current PR State

Two feature commits: 9a27e6c (original fix, luyua9) and e9531ce (review follow-up, magyargergo), plus 4 merge-from-main commits. 9 changed files total. Head SHA: e9531ce. All CI: 9/9 completed, 9/9 success.


Merge Status and Mergeability

CI: all green. Verified: main CI run (26211101572) — typecheck tsc --noEmit, unit tests (9379 passed, 1 skipped), E2E; scope-parity (TS + JS parity gates); Gitleaks; CodeQL; Dependency Review; Docker build; PR description check. No pending or failing checks. Merge is not blocked by CI. Conflict/mergeability status: branch is merge-safe based on clean CI and the verified merge commits.


Repository History Considered


Branch Hygiene Assessment

Acceptable. Four merge-from-main commits (8721f61, bbc9568, bafeaa6, 3960648) bring in unrelated main changes (worker resilience, eval-server fixes, FTS repair, etc.). Inspected merge commit 3960648 against both parents: the conflict in parsing-processor.ts was resolved cleanly — loggedQuarantineByPool and objectLiteralOwnerInfo code coexist correctly. The PR-specific diffs (git diff parent2..merge for each PR-specific file) match the intended changes exactly. No unrelated churn in the 9 PR-specific files.


Understanding of the Change

Structural fix (legacy DAG path — ast-helpers.ts, parsing-processor.ts, parse-worker.ts):

  • New helper findObjectLiteralBindingInfo does a two-phase AST walk from a method_definition node. Phase A tracks objectDepth and finds the enclosing variable_declarator (returns null for nested literals, function/class containers, and IIFEs). Phase B walks the declarator's ancestors and returns null if any function, class, or block-statement container precedes program/export_statement. Returns { ownerId: generateId(label, filePath + ':' + name) }.
  • Both parsing-processor.ts:793 and parse-worker.ts:2330–2331 now set ownerId on the symbol table entry for classless Method nodes to objectLiteralOwnerInfo?.ownerId.
  • Both emit HAS_METHOD from the owning Const/Variable graph node to the method node.

Scope-resolution fix (receiver-bound-calls.ts, walkers.ts, node-lookup.ts):

  • New Case 5 value-receiver bridge: after Cases 1–4 fail for lowercase receivers like fooService, findValueBindingInScope resolves the receiver as a non-class-like Variable def. resolveDefGraphId translates this to the canonical Const:filePath:name graph node ID (via simple-key fallback). model.methods.lookupAllByOwner(ownerGraphId, memberName) finds the method because ownerId matches. Edge emitted directly with picked.nodeId (= canonical graph node ID from the legacy parse phase).
  • isLinkableLabel extended with 'Const' so buildGraphNodeLookup registers Const nodes.
  • New findValueBindingInScope walker mirrors findClassBindingInScope but accepts non-class-like, non-callable defs.

Findings


Finding 1 — NOTED: Scope-Resolution Type Label Mismatch (Latent, Non-Blocking)

Severity: LOW

Evidence (verified):

scope-extractor.ts:696 maps both 'const' and 'variable' capture kinds to the label 'Variable' via normalizeNodeLabel. So a scope-resolution def for export const fooService = { ... } has def.type = 'Variable'. But findObjectLiteralBindingInfo generates the ownerId as generateId('Const', 'filePath:name') — using 'Const' because variable_declarator.parent.type === 'lexical_declaration'. This means:

In resolveDefGraphId(filePath, valueDef, nodeLookup):

  • valueDef.qualifiedName = 'fooService', valueDef.type = 'Variable'
  • Qualified key tried: <q>:filePath::Variable::fooServiceMISS (graph node is Const)
  • Falls back to simple key: filePath::fooServiceHIT → returns Const:filePath:fooService

The lookup succeeds via the simple-key fallback, and the owner graph ID resolves correctly. Verified by the integration test passing end-to-end. This is accidental correctness — if two bindings share the same simple name in the same file, or if the simple-key fallback were removed, this could silently break. However it works correctly under current codebase conventions.

Recommended follow-up: Consider either making normalizeNodeLabel('const') return 'Const' rather than 'Variable' (broader change requiring audit), or adding an explicit 'Variable'-to-'Const' translation step in Case 5 when the resolved value def has type === 'Variable' and the owner graph ID ends with Const:.

Blocks merge: NO. Integration tests prove correctness on the actual path.


Finding 2 — NOTED: Case 5 Hardcodes type: 'CALLS' for All Site Kinds (Latent, Non-Blocking)

Severity: LOW

Evidence (verified):

receiver-bound-calls.ts:762 emits type: 'CALLS' unconditionally in Case 5, regardless of site.kind. For read/write sites, tryEmitEdge would emit type: 'ACCESSES' (edges.ts:36–37). The dedup key also uses the CALLS: prefix hardcoded.

Why this is benign in practice: pickOverload checks model.methods.lookupAllByOwner first. For object-literal methods, only Method nodes are registered under the owner's ownerId (because objectLiteralOwnerInfo fires only when nodeLabel === 'Method' per parsing-processor.ts:536). For read/write access to a non-method property of an object literal, model.fields.lookupFieldByOwner returns undefined (no HAS_PROPERTY edges were added for non-method properties). So picked is undefined and Case 5 skips. No CALLS edge is emitted for ACCESSES scenarios.

Recommended follow-up: Use mapReferenceKindToEdgeType(site.kind) and match the dedup key prefix to the actual edge type, or gate Case 5 on site.kind === 'call' only if ACCESSES semantics for object-literal properties are not needed.

Blocks merge: NO. Harmless under current data model.


Finding 3 — INFO: JavaScript .js Files Have No Explicit Integration Test

Severity: INFO

Evidence (verified):

object-literal-owner-resolution.test.ts uses .ts fixture files only. The fix applies to both TypeScript and JavaScript because:

  1. findObjectLiteralBindingInfo uses tree-sitter AST nodes that are identical in JS and TS grammars (method_definition, object, variable_declarator)
  2. emitReceiverBoundCalls (Case 5) is language-agnostic, called from the shared pipeline/run.ts:353
  3. registry-primary-flag.ts:77 confirms JavaScript is in MIGRATED_LANGUAGES
  4. The CI scope-parity gate for JavaScript (scope-parity / javascript parity) passed ✅

Blocks merge: NO. CI scope-parity gate covers JavaScript at the parity level.


Finding 4 — INFO: Issue #1358 Class-Instance Sub-Case Out of Scope

Severity: INFO

Evidence: Issue #1358 mentions both export const fooService = { method() {} } and class FooService { method() {} }; export const fooService = new FooService(). The PR description explicitly scopes to "object-literal method shorthand" and tests only that pattern. The class-instance singleton sub-case is not fixed and not claimed as fixed. No test covers it.

Recommended action: Add a tracking comment in the PR linking to a follow-up issue for the class-instance singleton pattern, or update the PR description to explicitly document the remaining sub-case. Not required for this PR to merge.

Blocks merge: NO. Partial fix is documented. The PR does not claim to fully close #1358 for the class-instance pattern.


Finding 5 — CONFIRMED CLEAN: Direct Edge Emission Invariants

Severity: CONFIRMED OK

Case 5's direct graph.addRelationship (lines 758–765) was flagged as a candidate bypass of graph bridge invariants. Verified:

  • Graph node IDs: callerGraphId comes from resolveCallerGraphId (same as tryEmitEdge). picked.nodeId is the symbol-table nodeId passed by parsing-processor.ts (= generateId(nodeLabel, filePath + ':' + name)) — the canonical legacy-phase graph node ID.
  • Dedup key format: CALLS:${callerGraphId}->${picked.nodeId}:${line}:${col} matches tryEmitEdge's format for call sites exactly (${edgeType}:${callerGraphId}->${targetGraphId}:${line}:${col}).
  • Edge ID: rel:${dedupKey} matches tryEmitEdge's rel:${dedupKey}.
  • handledSites: set on the same path, preventing emitReferencesViaLookup double-emission ✓
  • seen: prevents within-pass duplication ✓

The reason for not using tryEmitEdge directly (stated in the comment at lines 729–735) is that object-literal method defs don't have class-qualified qualifiedName, so resolveDefGraphId would fall through to simple-key. This works but the comment claims it "cannot round-trip" which is slightly overstated — it could, via the simple-key path. The direct emission is semantically equivalent for call sites and is correct.

Blocks merge: NO.


PR-Specific Assessment Sections

Lane 1 — Parser Ownership

Status: PASS.

findObjectLiteralBindingInfo two-phase walk inspected line-by-line (ast-helpers.ts:468–528). Phase A correctly increments objectDepth on object nodes, exits on variable_declarator with depth >= 1 (rejecting depth > 1), and returns null on function/class containers before the declarator (IIFEs). Phase B correctly rejects function, class, and all BLOCK_SCOPE_BOUNDARY_TYPES ancestors between the declarator and program/export_statement. The ownerLabel heuristic (variable_declaration'Variable', otherwise → 'Const') matches getLabelFromCaptures and the tree-sitter query capture names (@definition.const for lexical_declaration, @definition.variable for variable_declaration).

Tested: 13 integration cases in ast-helpers-object-literal-binding.test.ts against a real tree-sitter TypeScript parser. All boundary conditions covered.

Residual risk: Object.assign({}, { method() {} }) pattern would falsely attribute (Phase A finds the declarator via an intermediate call_expression). Not listed in the PR-specific DoD and not a new regression relative to pre-PR behavior. Acceptable known limitation.

Lane 2 — Graph Structural

Status: PASS.

Both parsing-processor.ts:813–826 and parse-worker.ts:2365–2378 emit HAS_METHOD using ownerIdForMemberEdge = enclosingClassId ?? objectLiteralOwnerInfo?.ownerId ?? null. Edge IDs use generateId(memberEdgeType, ownerIdForMemberEdge + '->' + nodeId) — deterministic and dedup-safe. Reason field correctly identifies object-literal origin vs class-origin edges. Code is structurally symmetric between the two paths.

Lane 3 — Symbol-Table

Status: PASS.

parsing-processor.ts:793: ownerId: enclosingClassId ?? objectLiteralOwnerInfo?.ownerId ?? undefined
parse-worker.ts:2330–2331: ...((enclosingClassId ?? objectLiteralOwnerInfo?.ownerId) ? { ownerId: ... } : {})

Both paths set ownerId to the canonical graph node ID. The method registry keys on ownerNodeId\0methodName, so model.methods.lookupAllByOwner(ownerGraphId, 'getUser') finds the method correctly when ownerGraphId = generateId('Const', 'filePath:fooService').

Lane 4 — Scope-Resolution

Status: PASS (with noted type-mismatch, Finding 1).

Case 5 flow verified end-to-end:

  1. findValueBindingInScope (walkers.ts:230–261) correctly walks scopes including imported bindings via lookupBindingsAt — imported fooService is visible at the consumer's scope ✓
  2. resolveDefGraphId succeeds via simple-key fallback (type mismatch noted in Finding 1 but correct in practice) ✓
  3. model.methods.lookupAllByOwner(ownerGraphId, memberName) finds the method ✓
  4. picked.nodeId = canonical graph node ID ✓
  5. CALLS edge emitted with consistent dedup key and edge ID format ✓
  6. handledSites prevents double-emission ✓

Lane 5 — Worker Parity

Status: PASS.

Sequential and worker code paths are structurally identical for objectLiteralOwnerInfo. Integration test B (object-literal-owner-resolution.test.ts:136–178) compares HAS_METHOD and CALLS edge sets between sequential and worker pipeline runs, asserting expect(workerEdges).toEqual(seqEdges). Gated on hasDistWorker (CI builds dist/parse-worker.js before running integration tests).

Lane 6 — Regression/False-Positive

Status: PASS.

Negative cases proven by tests:

  • Local const inside function body → null (Phase B rejects function_declaration ancestor) ✓
  • Local const inside arrow function → null (Phase A rejects arrow_function before declarator) ✓
  • Local const inside class constructor → null (Phase A rejects class body container) ✓
  • Nested object literal method → null (Phase A rejects objectDepth > 1) ✓
  • Top-level method alongside nested literal → correctly binds to outer ✓
  • Block-scoped inside if body → null (Phase B rejects statement_block) ✓
  • Block-scoped inside for-of body → null ✓
  • Block-scoped inside try body → null ✓
  • IIFE-wrapped → null (Phase A rejects arrow_function before declarator) ✓
  • Assignment expression (no declarator) → null without throwing ✓
  • Integration test C: local literal in function body emits no HAS_METHOD and ownerId = undefined on the method node ✓
  • Integration test D: nested literal binds only top-level method to outer, not the nested method ✓

Lane 7 — CI/Test

Status: PASS.

Tests cover the real runtime path — no mocks bypass ingestion or graph construction. Assertions use exact toBe/toEqual per DoD §2.7. object-literal-owner-resolution.test.ts:127–131 proves the CALLS edge exists (the canonical proof for issue #1358's impact-analysis fix). parsing-worker-fallback.test.ts:147–188 proves the structural assertion in sequential mode.

All 9 CI checks pass on the head commit. 9379/9379 tests passed (1 pre-existing skip on Ruby block parameter inference, unrelated).

Lane 8 — Branch Hygiene

Status: PASS.

Merge commit 3960648 (most recent, bringing in PR #1693 worker resilience) verified: parsing-processor.ts conflict resolved correctly — loggedQuarantineByPool from main and objectLiteralOwnerInfo from PR branch coexist without interference. No unrelated changes appear in the 9 PR-specific files when comparing git diff parent2..merge for each merge commit.


Back-and-Forth Avoided by Verifying

  • Owner ID label consistency (const/let/var): verified getLabelFromCaptures, normalizeNodeLabel, tree-sitter queries, and findObjectLiteralBindingInfo all agree — lexical_declaration'Const', variable_declaration'Variable'. No mismatch for supported forms.
  • resolveDefGraphId path for Const: verified it works via simple-key fallback despite the scope-resolution type being 'Variable'. Confirmed by passing integration test.
  • picked.nodeId correctness: traced symbolTable.add(nodeId)SymbolDefinition.nodeIdmodel.methods.lookupAllByOwnerpicked.nodeId. All the same canonical graph node ID from generateId(nodeLabel, filePath + ':' + name).
  • Worker dispatch threshold: workerThresholdsForTest: { minFiles: 1, minBytes: 1 } in test B forces worker mode for the 2-file fixture — confirmed the worker path actually runs.
  • JavaScript coverage: receiver-bound-calls.ts is called from the shared pipeline, not from the TypeScript-specific scope-resolver. JS is covered generically.
  • Merge commit conflict resolution: inspected git diff parent1..merge vs git diff parent2..merge for the files touched by the PR. Resolution is correct.

Open Questions

None. All verification points were resolvable from the repository.


Final Verdict

production-ready with minor follow-ups

The PR correctly and completely fixes the production symptom reported in issue #1358 for the export const fooService = { method() {} } pattern: HAS_METHOD edges are emitted in both the sequential and worker parse paths, ownerId is correctly set in the symbol table, and Case 5 in receiver-bound-calls.ts connects cross-file call sites to the object-literal method nodes, producing CALLS edges that make gitnexus_impact traversal accurate. All originally-blocking findings from the prior review (F1: caller resolution, F2: boundary guard, F3: test coverage, F4: dead code) are addressed and verified. The two noted findings (type-label mismatch that resolves via simple-key fallback; CALLS hardcoding for read/write sites) are benign under the current data model and do not affect the fix's correctness. CI is green across all 9 required gates with 9379 tests passing. Branch hygiene is acceptable.

@magyargergo magyargergo changed the title Link object literal methods to exported bindings feat(ingestion): Link object literal methods to exported bindings May 21, 2026
…wner resolution

Multi-agent code review on the prior commit surfaced 7 actionable findings,
all walked through and applied here. None change observable behavior for
issue abhigyanpatwari#1358's fix; all harden correctness, predicate stability, and test
signal.

- abhigyanpatwari#1 (P1 / 3-reviewer corroboration): Case 5 in receiver-bound-calls.ts no
  longer hand-builds graph.addRelationship + a dedup key. New
  tryEmitEdgeWithExplicitTargetId in edges.ts takes a pre-resolved target
  id (the canonical Method nodeId from the parser) and reuses every
  invariant of tryEmitEdge: dedup-key format, collapse-flag honoring,
  caller-id resolution, rel-id shape, mapReferenceKindToEdgeType for
  read/write ACCESSES. This also lands the adversarial reviewer's "F2"
  follow-up (hardcoded type: 'CALLS' for non-call sites) for free.

- abhigyanpatwari#2 (P2 cross-reviewer): findValueBindingInScope's predicate inverted
  from denylist ("not class-like and not callable") to explicit allowlist
  matching reconcileOwnership's registration set:
  Const | Variable | Property | Static. Extracted as isOwnableValueLabel
  so future NodeLabel additions require an explicit opt-in.

- abhigyanpatwari#6 (P2): walkScopeChain<T>() extracted; both findClassBindingInScope
  and findValueBindingInScope now route through it. Local scope.bindings
  are exhausted BEFORE lookupBindingsAt (imported/augmented) at every
  scope level — preserves JavaScript lexical scoping where a local const
  shadows an imported binding of the same name. Behavior was already
  correct in findClassBindingInScope but was implicit; now it is the
  walker's explicit, documented contract.

- abhigyanpatwari#7 (P2): scope-walker duplication closed. findClassBindingInScope and
  findValueBindingInScope reduce to thin wrappers over walkScopeChain
  with their respective predicate. findClassBindingInScope keeps its
  qualifiedNames + dotted-name fallback tail.

- abhigyanpatwari#3 (P2): parse-worker.ts hoists `const ownerId = enclosingClassId ??
  objectLiteralOwnerInfo?.ownerId` once before the symbol push, dropping
  the duplicated coalesce + `as string` cast. Matches the cast-free
  pattern at parsing-processor.ts:793. HAS_METHOD emit site reuses the
  same hoisted local.

- abhigyanpatwari#4 (P2): object-literal-owner-resolution.test.ts Test A's CALLS-edge
  assertion no longer matches by name alone. .toEqual now pins the
  canonical target id (Method:src/service.ts:getUser#1 via generateId),
  confidence (0.85), and reason ('import-resolved'). A regression that
  emits the edge at confidence=0, with the wrong reason, or against a
  phantom Method node now fails the test.

- abhigyanpatwari#5 (P2): worker-parity test adds a CI tripwire — when CI=1 and
  dist/parse-worker.js is missing, throw at module top with a clear
  message. Locally, skipIf(!hasDistWorker) keeps the fast-iteration
  experience; CI cannot pass with U3 (worker-path ownerId) unverified.

Verification: tsc --noEmit clean. Targeted regression sweep on
ast-helpers-object-literal-binding (13), object-literal-owner-resolution
(9), has-method (60), cross-file-binding (40) — 122/122 pass. Full unit
sweep: 6056/6056. Integration suite: 1 pre-existing Windows-flake in
worker-pool.test.ts (passes 28/28 in isolation) unrelated to this diff.
…G (PR abhigyanpatwari#1718 review F1)

Eliminates the architectural fragility surfaced by PR abhigyanpatwari#1718's adversarial review
Finding 1. Previously, normalizeNodeLabel('const') returned 'Variable' while
the legacy DAG parse phase emits 'Const' graph nodes (via @definition.const
capture for lexical_declaration). PR abhigyanpatwari#1718's Case 5 value-receiver bridge
resolved correctly only because resolveDefGraphId happened to fall back to
simpleKey after the qualified-key miss — accidental correctness.

After this change, scope-resolution defs for `const x = ...` declarations
report def.type === 'Const', matching the graph node label. resolveDefGraphId's
qualified-key path now hits on the first try; the simple-key fallback is no
longer load-bearing for value receivers and can be tightened in future without
silently breaking Case 5.

Audit completeness verification:
- Grep `\bVariable\b` across src/core/ingestion/scope-resolution/ surfaced two
  consumer sites that already accept both labels: reconcile-ownership.ts:101+168
  (`def.type === 'Variable' || def.type === 'Const' || ...`) and
  walkers.ts:207 isOwnableValueLabel (`Const | Variable | Property | Static`).
  No language hook in src/core/ingestion/languages/ branches on
  `def.type === 'Variable'` for what's actually a const declaration.
- Sentinel stress test (the full unit + integration suite run with the
  renamed label in place): 6137/6137 unit tests pass; 2967/2967 integration
  tests pass. One pre-existing Windows-only flake on worker-pool.test.ts when
  run alongside the full integration suite (passes 28/28 in isolation,
  unrelated to scope-extractor — same flake observed before this diff).

The variable mapping (`'variable' → 'Variable'`) is preserved for `var`
declarations, matching the legacy DAG's `@definition.variable` capture for
variable_declaration. The split now mirrors the parse-phase capture
distinction exactly.

Per plan docs/plans/2026-05-21-002-feat-pr1718-followups-class-instance-and-label-normalization-plan.md
U4 + U5. T1 (class-instance singleton resolution from issue abhigyanpatwari#1358's second
sub-case) is deferred to a standalone pre-plan investigation, not shipped
here.
@magyargergo magyargergo self-assigned this May 21, 2026
… singleton sub-cases

Closes the remaining sub-cases of issue abhigyanpatwari#1358 surfaced by PR abhigyanpatwari#1718's
adversarial review (Finding 4, NOTED): the class-instance singleton
(`export const fooService = new FooService();`) and the factory-pattern
singleton (`export const fooService = makeFooService();`).

Pre-plan investigation (per docs/plans/2026-05-21-002 § "Pre-Plan
Investigation Task (T1)") confirmed Outcome A for both patterns — they
already resolve end-to-end through scope-resolution's
`@type-binding.constructor` capture (languages/typescript/query.ts:489-511)
+ `propagateImportedReturnTypes` chain-follow
(scope-resolution/passes/imported-return-types.ts:114) + receiver-bound
Case 4 simple typeBinding lookup (receiver-bound-calls.ts:625). The
mechanism was wired correctly before this session; the regression-net
wasn't.

This test pins the behavior:
- Pattern 1: `caller → FooService.getUser` CALLS edge with
  confidence 0.85 and reason 'import-resolved'
- Pattern 2: same edge shape via factory chain-follow (the
  `@type-binding.alias` capture for `const u = find()` style)

Both assertions use exact `.toEqual([{...}])` shape pinning so a future
regression that targets a phantom Method node, emits at lower confidence,
or drops the cross-file import-resolved reason fails loudly.

Verification: 5/5 pass, 127/127 in targeted regression sweep including
object-literal-owner-resolution.test.ts, ast-helpers-object-literal-
binding.test.ts, has-method.test.ts, and cross-file-binding.test.ts.

No production code change. The class methods get a class-qualified node id
(`Method:src/service.ts:FooService.getUser#1`) distinguishing them from
same-name methods on other classes — distinct from the bare-name node id
shape PR abhigyanpatwari#1718's object-literal case uses.
…age for TS/JS (issue abhigyanpatwari#1358)

Closes the remaining sub-cases of issue abhigyanpatwari#1358 surfaced by PR abhigyanpatwari#1718's
adversarial review (Finding 4). PR abhigyanpatwari#1718 fixed object-literal-shorthand
singletons (`export const fooService = { getUser() {} }`); this commit adds
parallel coverage for the two other singleton shapes that resolve through
the existing scope-resolution chain:

  // Pattern 1 — class-instance singleton
  export class FooService { getUser(id) { ... } }
  export const fooService = new FooService();

  // Pattern 2 — factory-pattern singleton
  export class FooService { getUser(id) { ... } }
  export function makeFooService() { return new FooService(); }
  export const fooService = makeFooService();

Pre-plan investigation (per local plan docs/plans/2026-05-21-002 § "Pre-Plan
Investigation Task (T1)") confirmed Outcome A — both patterns already
resolve end-to-end through:
  - `@type-binding.constructor` capture (languages/{typescript,javascript}/
    query.ts) seeds `fooService → FooService` at parse time
  - `propagateImportedReturnTypes` (scope-resolution/passes/
    imported-return-types.ts:114) mirrors the typeBinding cross-file
  - Receiver-bound Case 4 simple typeBinding lookup
    (scope-resolution/passes/receiver-bound-calls.ts:625) MRO-walks
    FooService and emits the CALLS edge to getUser

Tests added per language × pattern (5 each, 10 total):
- node existence (Class, Method, Function, Const, plus Function for the
  factory pattern's `makeFooService`)
- HAS_METHOD edge from class to method (class-instance variant)
- CALLS edge from caller to `getUser` with `targetFilePath: 'src/service.{ts,js}'`,
  `reason: 'import-resolved'`, `confidence: 0.85` — exact `.toEqual([{...}])`
  shape pinning so a regression that emits at lower confidence or drops the
  cross-file reason fails loudly

Fixtures placed under the existing `test/fixtures/lang-resolution/` convention.
Tests appended to `test/integration/resolvers/{typescript,javascript}.test.ts`,
matching the in-file pattern of every other resolver scenario.

Also supersedes and removes the standalone
`test/integration/class-instance-and-factory-singleton-resolution.test.ts`
introduced earlier in this PR session (`0df91b77`) — the proper home for
language-resolver scenarios is the per-language resolver test file alongside
similar fixtures (`javascript-self-this-resolution`, `javascript-cross-file`,
`typescript-tsconfig-paths`, etc.). One canonical location for the scenario,
not two.

Verification: 10/10 new singleton tests pass; 297/297 full TS+JS resolver
suite pass (no regression in any existing resolver test).
…arity (CI run 26223603426)

The class-instance and factory-pattern singleton CALLS-edge resolution
tests added in c8e573b rely on scope-resolution-only mechanisms
(`@type-binding.constructor` capture + `propagateImportedReturnTypes`
mirror + receiver-bound Case 4). The `scope-parity / typescript parity`
and `scope-parity / javascript parity` CI jobs run with
`REGISTRY_PRIMARY_TYPESCRIPT=0` / `REGISTRY_PRIMARY_JAVASCRIPT=0` and
exercise the legacy DAG path, which has no cross-file constructor-derived
typeBinding propagation. Verified by job 77202610819 (TS parity) and
77202610869 (JS parity) failing with:

  × resolves caller.fooService.getUser() to FooService.getUser via constructor-inferred typeBinding
  × resolves caller.fooService.getUser() through the factory chain to FooService.getUser

Note: my local Windows shell-prefix env-var invocation did not propagate
the flag into vitest workers correctly (the cpp parity gate's 47-skipped
behavior masked the issue when I ran an ad-hoc comparison), so the
empirical "both modes pass" finding I posted earlier was wrong. CI is the
source of truth.

Changes:
- test/integration/resolvers/helpers.ts: add `typescript` and `javascript`
  entries to `LEGACY_RESOLVER_PARITY_EXPECTED_FAILURES` for the 2 CALLS-edge
  resolution tests in each language. Node-existence and HAS_METHOD
  assertions are NOT excluded — those pass under legacy DAG (parser-level
  emission is intact).
- test/integration/resolvers/typescript.test.ts: drop the `it` import from
  vitest; replace with `const it = createResolverParityIt('typescript');`
  shadow (matches the c/cpp/csharp/go pattern at the top of those files).
- test/integration/resolvers/javascript.test.ts: same shadow with
  `createResolverParityIt('javascript')`.

Verification:
- Default mode (registry-primary): 297/297 TS+JS resolver tests pass.
- Legacy DAG mode: the 4 listed singleton CALLS-edge tests will skip; all
  other singleton assertions (node existence + HAS_METHOD edge) continue
  to run and pass under both modes.
@magyargergo magyargergo merged commit dd35273 into abhigyanpatwari:main May 21, 2026
32 of 33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeScript: export const fooService = { async method() {} } produces zero edges (no HAS_PROPERTY, no incoming CALLS)

2 participants