mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
feat(javascript): emit ACCESSES for bare-identifier reads of module-scope consts
A constant read only as a bare identifier — `Math.max(LIMIT, n)`, a default parameter value, `return LIMIT` — minted no reference site at all, because JS captured only `@reference.read.member`, which requires a receiver a bare identifier does not have. So "who uses this constant?", the question behind every dead-code trim and constants refactor, answered with a confident zero in both directions. The rest of the machinery was already in place: `FIELD_KINDS` accepts `Const`, the scope query already declares it via `@declaration.const`, and `read` maps to ACCESSES for any resolved target. This adds the missing capture in VALUE POSITIONS ONLY (call arguments, default-parameter values, return statements) — a blanket `(identifier)` rule would mint a site for every token in the file, which is unaffordable at repo scale and would keep alive the block-local symbols `pruneLocalSymbols` exists to drop. Cross-file readers are NOT yet covered: the site exists and a call through the same import statement resolves, but a value-kind def does not link across the import edge. Recorded as a todo with the investigation. PARSE_CACHE_VERSION bumped 44 -> 45: this is parse-time capture emission, so a warm cache replays the pre-change capture set and the new edges never appear — observed directly, a full `analyze --force` produced a byte-identical graph until the cache was cleared by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8ddb9c1d2a
commit
be75ee373f
5 changed files with 125 additions and 1 deletions
|
|
@ -589,6 +589,17 @@ export const JAVASCRIPT_SCOPE_QUERY = `
|
|||
|
||||
(object
|
||||
(shorthand_property_identifier) @reference.name @reference.property-key @reference.value-ref)
|
||||
|
||||
;; Bare-identifier reads (A2). VALUE POSITIONS ONLY — a blanket
|
||||
;; \`(identifier)\` rule would mint a site for every token in the file.
|
||||
(arguments
|
||||
(identifier) @reference.name @reference.read.identifier)
|
||||
|
||||
(assignment_pattern
|
||||
right: (identifier) @reference.name @reference.read.identifier)
|
||||
|
||||
(return_statement
|
||||
(identifier) @reference.name @reference.read.identifier)
|
||||
`;
|
||||
|
||||
/** JSX-only suffix — appended when compiling against the JSX grammar for .jsx files. */
|
||||
|
|
|
|||
|
|
@ -248,7 +248,25 @@ import type { ParseWorkerResult } from '../core/ingestion/workers/parse-worker.j
|
|||
// them. Only comparing against origin/main at MERGE time surfaces it.
|
||||
// PR #2840 (Objective-C, draft) still claims 44 as well — it must move too.
|
||||
// RE-CHECK AGAINST origin/main IMMEDIATELY BEFORE MERGING.
|
||||
const SCHEMA_BUMP = 45;
|
||||
// 45 -> 46 for the JavaScript bare-identifier read captures (A2), which emit
|
||||
// `@reference.read.identifier` in value positions (call arguments,
|
||||
// default-parameter values, return statements) so a module-scope `const` read
|
||||
// only by bare name finally mints a reference site, plus the object-literal
|
||||
// `@definition.property` rule and the TypeScript shape-member captures. All
|
||||
// PARSE-TIME emission, so a warm cache serves entries carrying none of those
|
||||
// matches and the new nodes and edges never appear — observed directly while
|
||||
// developing: a full `analyze --force` produced a byte-identical graph and read
|
||||
// as a failed hypothesis until the cache was cleared by hand.
|
||||
//
|
||||
// This branch originally took 45 and it COLLIDED: #2837 above merged first and
|
||||
// claimed it. The TENTH entry in this ledger and the FOURTH exact clash, caught
|
||||
// exactly as the note above says it must be — by comparing against origin/main
|
||||
// at merge time, not at review time. The pin test cannot catch it: both sides
|
||||
// asserted `toBe(45)`, which passes while main is already 45, so two capture
|
||||
// schemas would have shared one PARSE_CACHE_VERSION and the durable ParsedFile
|
||||
// store would have replayed pre-fix ParsedFiles verbatim for one of them.
|
||||
// RE-CHECK AGAINST origin/main IMMEDIATELY BEFORE MERGING.
|
||||
const SCHEMA_BUMP = 46;
|
||||
const GITNEXUS_PKG_VERSION = (() => {
|
||||
try {
|
||||
// package.json sits at gitnexus/package.json — two levels up from
|
||||
|
|
|
|||
15
gitnexus/test/fixtures/lang-resolution/javascript-const-references/config.js
vendored
Normal file
15
gitnexus/test/fixtures/lang-resolution/javascript-const-references/config.js
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// A2: a module-scope const referenced only as a bare identifier.
|
||||
const DEFAULT_FETCH_LIMIT = 500;
|
||||
|
||||
// Inline-exported form — the common spelling.
|
||||
export const INLINE_LIMIT = 250;
|
||||
|
||||
export function fetchAll(limit = DEFAULT_FETCH_LIMIT) {
|
||||
return Math.max(DEFAULT_FETCH_LIMIT, limit);
|
||||
}
|
||||
|
||||
export function pageSize() {
|
||||
return DEFAULT_FETCH_LIMIT;
|
||||
}
|
||||
|
||||
export { DEFAULT_FETCH_LIMIT };
|
||||
16
gitnexus/test/fixtures/lang-resolution/javascript-const-references/consumer.js
vendored
Normal file
16
gitnexus/test/fixtures/lang-resolution/javascript-const-references/consumer.js
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { DEFAULT_FETCH_LIMIT, INLINE_LIMIT, pageSize } from './config.js';
|
||||
|
||||
// A2 cross-file: a named-import reference to a module-scope const.
|
||||
export function consumerLimit() {
|
||||
return DEFAULT_FETCH_LIMIT;
|
||||
}
|
||||
|
||||
// Control: same shape, but the const was exported inline.
|
||||
export function consumerInline() {
|
||||
return INLINE_LIMIT;
|
||||
}
|
||||
|
||||
// Control: a cross-file CALL through the same import statement resolves today.
|
||||
export function consumerCall() {
|
||||
return pageSize();
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* A2 — references to a module-scope `const` must produce edges.
|
||||
*
|
||||
* A constant read only as a BARE IDENTIFIER (`Math.max(LIMIT, n)`, a default
|
||||
* parameter value, `return LIMIT`) produced no reference site at all, so
|
||||
* "who uses this constant?" — the question behind every dead-code trim and
|
||||
* constants refactor — answered with a confident zero rather than "unknown".
|
||||
*
|
||||
* The registries already accept it (`FIELD_KINDS` includes `Const`) and the
|
||||
* scope query already declares it (`@declaration.const`), so this is about the
|
||||
* reference SITE existing: JS/TS captured only `@reference.read.member`, which
|
||||
* requires a receiver a bare identifier does not have.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import path from 'path';
|
||||
import { FIXTURES, getRelationships, runPipelineFromRepo, type PipelineResult } from './helpers.js';
|
||||
|
||||
describe('JavaScript module-scope const references (A2)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'javascript-const-references'),
|
||||
() => {},
|
||||
);
|
||||
}, 60000);
|
||||
|
||||
const readersOfConst = (): Set<string> =>
|
||||
new Set(
|
||||
getRelationships(result, 'ACCESSES')
|
||||
.filter((e) => e.target === 'DEFAULT_FETCH_LIMIT')
|
||||
.map((e) => e.source),
|
||||
);
|
||||
|
||||
it('emits ACCESSES from same-file readers of the const', () => {
|
||||
const readers = readersOfConst();
|
||||
// fetchAll reads it twice (default param + Math.max); pageSize returns it.
|
||||
expect(readers).toContain('fetchAll');
|
||||
expect(readers).toContain('pageSize');
|
||||
});
|
||||
|
||||
// Cross-file is NOT yet covered. The reference site exists (the capture
|
||||
// fires on `return DEFAULT_FETCH_LIMIT` in consumer.js, verified against the
|
||||
// raw query) and a CALL through the very same import statement resolves
|
||||
// (`consumerCall → pageSize`, reason `import-resolved`), so the gap is
|
||||
// specific to linking a value-kind def across the import edge. Prime
|
||||
// suspect: exported-def resolution is callable-only — `findExportedDefByName`
|
||||
// returns a def only when `def.type` is `Function`/`Method`
|
||||
// (scope/walkers.ts:1323) and its workspace fallback index is
|
||||
// `exportedCallableByName`. Both export spellings (`export const X` and
|
||||
// `export { X }`) fail identically, so it is not the export syntax.
|
||||
it.todo('emits an edge for the cross-file named-import reader');
|
||||
|
||||
it('targets the Const node itself, not a same-named local', () => {
|
||||
const toConst = getRelationships(result, 'ACCESSES').filter(
|
||||
(e) => e.target === 'DEFAULT_FETCH_LIMIT',
|
||||
);
|
||||
expect(toConst.length).toBeGreaterThan(0);
|
||||
for (const e of toConst) {
|
||||
expect(e.targetLabel).toBe('Const');
|
||||
expect(e.targetFilePath).toContain('config.js');
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue