mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-13 23:14:20 +00:00
* fix(ingestion): stop emitting phantom Function defs for array-method callbacks The HOC-wrapped-arrow scope-query pattern (`const X = HOC(args => ...)`), added for React idioms such as forwardRef/memo/useCallback, also matched array higher-order-method callbacks like `const x = arr.map(a => ...)`. Those produced a spurious `@declaration.function` named after the binding, on top of its value def, so calls inside the callback attributed to a phantom `Function:x` instead of the enclosing scope. - Add a shared `isArrayMethodCallbackArrow` detector (`ARRAY_CALLBACK_METHODS` blocklist) and suppress the `@declaration.function` emit-side in both the JS and TS scope-captures emitters, leaving the value binding as the sole def. - Add `selectNodeBearingDef` in scope-extractor: the tested collapse-rule contract (function-like > value > first) the deferred node-creation migration will consume to keep one graph node per binding. This corrects the registry-primary scope model and CALLS-edge attribution (calls inside array-method callbacks now source from the enclosing File scope). The duplicate graph *node* itself is still created by the legacy parse-worker path and is removed by the follow-up node-creation migration. Refs #1876 Co-authored-by: Cursor <cursoragent@cursor.com> * test(ingestion): strengthen array-callback coverage; document receiver-blind suppression Follow-ups from the production-readiness review of PR #1906: - array-callback.ts: document that isArrayMethodCallbackArrow is receiver-blind — an in-set method name on a NON-array receiver (Map/Set.forEach, RxJS observable.map, query-builder .sort, lodash chain .filter) is also suppressed. Accepted limitation, not a bug: the binding holds the call's result value, not a callable. - captures unit tests (JS + TS): add a non-array-receiver characterization case, and extend the it.each lists to cover findLast, findLastIndex, reduceRight — the full 13-entry ARRAY_CALLBACK_METHODS set is now exercised in both languages. - js-array-method-callback-attribution integration test: tighten the File-sourced CALLS assertions from toBeGreaterThan(0) to toHaveLength(1) (now also catches over-attribution). - scope-extractor.ts: note that the dead selectNodeBearingDef export is intentional and tracked by #1876 (deferred node-creation migration). Comment-and-test only; no production behavior change. Verified locally: tsc clean, prettier/eslint clean, captures unit 106 passed, scope-extractor 31 passed, integration 3 passed. Refs #1876 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
81 lines
3.4 KiB
TypeScript
81 lines
3.4 KiB
TypeScript
/**
|
|
* JavaScript: CALLS-edge attribution for calls inside array higher-order-
|
|
* method callbacks (issue #1876).
|
|
*
|
|
* `const exportData = accountsList.map(account => transform(account))` matches
|
|
* the HOC-wrapped-arrow declaration pattern, so before this fix the JS scope
|
|
* model emitted a phantom `Function:exportData` for the `.map` callback (on
|
|
* top of the value binding). Calls nested in the callback (`transform`) then
|
|
* attributed to that phantom `Function` instead of the enclosing scope.
|
|
*
|
|
* U1 drops the `@declaration.function` for array-method callbacks, so the
|
|
* binding is value-only and the inner call falls through to the File scope —
|
|
* exactly the Zustand module-level-call behavior already pinned for TS.
|
|
*
|
|
* SCOPE: this asserts the registry-primary CALLS-edge ATTRIBUTION change only.
|
|
* The duplicate *graph node* (`Function:exportData`) is created by the legacy
|
|
* parse-worker node path, which this change does not touch; collapsing it is
|
|
* the deferred node-creation migration. Accordingly this file makes NO node-
|
|
* count assertion.
|
|
*
|
|
* Registry-primary-only correctness win: under the forced-legacy parity flag
|
|
* (`REGISTRY_PRIMARY_JAVASCRIPT=0`) the legacy DAG still emits the phantom
|
|
* attribution, so the suite is skipped there (mirrors the per-language
|
|
* expected-failure handling in `resolvers/helpers.ts`).
|
|
*/
|
|
import { describe, it, expect, beforeAll } from 'vitest';
|
|
import path from 'path';
|
|
import {
|
|
FIXTURES,
|
|
getRelationships,
|
|
isLegacyResolverParityRun,
|
|
runPipelineFromRepo,
|
|
type PipelineResult,
|
|
} from './resolvers/helpers.js';
|
|
|
|
describe.skipIf(isLegacyResolverParityRun('javascript'))(
|
|
'JavaScript array-method-callback CALLS attribution (#1876)',
|
|
() => {
|
|
let result: PipelineResult;
|
|
|
|
beforeAll(async () => {
|
|
result = await runPipelineFromRepo(
|
|
path.join(FIXTURES, 'javascript-array-method-callback'),
|
|
() => {},
|
|
);
|
|
}, 60000);
|
|
|
|
it('control: run() body calls transform directly (resolver is wired)', () => {
|
|
const calls = getRelationships(result, 'CALLS').filter((c) => c.target === 'transform');
|
|
expect(calls.map((c) => `${c.source} → ${c.target}`)).toContain('run → transform');
|
|
});
|
|
|
|
it('call inside .map callback attributes to File, not a phantom Function:exportData', () => {
|
|
const calls = getRelationships(result, 'CALLS').filter((c) => c.target === 'transform');
|
|
const fromExportData = calls.filter((c) => c.source === 'exportData');
|
|
expect(
|
|
fromExportData,
|
|
'transform must NOT be attributed to exportData (phantom Function)',
|
|
).toEqual([]);
|
|
const fromFile = calls.filter((c) => c.sourceLabel === 'File');
|
|
expect(
|
|
fromFile,
|
|
'the .map callback call to transform must source from the File node (exactly once)',
|
|
).toHaveLength(1);
|
|
});
|
|
|
|
it('call inside .find callback attributes to File, not a phantom Function:firstActive', () => {
|
|
const calls = getRelationships(result, 'CALLS').filter((c) => c.target === 'predicate');
|
|
const fromFirstActive = calls.filter((c) => c.source === 'firstActive');
|
|
expect(
|
|
fromFirstActive,
|
|
'predicate must NOT be attributed to firstActive (phantom Function)',
|
|
).toEqual([]);
|
|
const fromFile = calls.filter((c) => c.sourceLabel === 'File');
|
|
expect(
|
|
fromFile,
|
|
'the .find callback call to predicate must source from the File node (exactly once)',
|
|
).toHaveLength(1);
|
|
});
|
|
},
|
|
);
|