mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
feat(javascript): index object-literal keys of a named object as Property nodes
Idiomatic JS models configuration as an object literal, not a class, but
Property definition nodes existed only for DECLARED CLASS FIELDS. A config
field therefore had no symbol at all: `context({name: 'exitMinAtrMult'})`
answered "not found" for a field read and written throughout a live code
path, and ACCESSES had no target to point at.
Both halves are added for keys of a literal BOUND TO A VARIABLE — the parse
query mints the graph node, the scope query mints the def the resolver can
aim at. Unbound literals are deliberately excluded: an inline call argument
or a JSX prop bag is call-site data, not a named surface other code
references, so a node per key there would add volume without adding an
answerable question.
This lands the definition-node half only. The ACCESSES edges still require
receiver resolution — typing the const that holds the literal to the
literal's scope for the precise case, and name-based matching at reduced
confidence for the untyped-param (option bag) case. Both are recorded as
todos with the mechanism each needs.
Also records a trap that cost a wrong conclusion: under vitest the parse
worker runs the BUILT dist code (parse-impl resolves parse-worker.js, absent
under src/, and falls back to dist), so parse-query changes are invisible to
tests until `npm run build`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7eabdd46d6
commit
3d9ed40276
3 changed files with 81 additions and 19 deletions
|
|
@ -107,6 +107,15 @@ export const JAVASCRIPT_SCOPE_QUERY = `
|
|||
(field_definition
|
||||
property: (property_identifier) @declaration.name) @declaration.property
|
||||
|
||||
;; Object-literal keys of a NAMED object (A1/A5) — the scope-resolution half of
|
||||
;; the same rule in TYPESCRIPT/JAVASCRIPT_QUERIES. The parse query mints the
|
||||
;; Property NODE; this mints the DEF the resolver can point a read/write at.
|
||||
(variable_declarator
|
||||
name: (identifier)
|
||||
value: (object
|
||||
(pair
|
||||
key: (property_identifier) @declaration.name) @declaration.property))
|
||||
|
||||
;; Declarations — free functions
|
||||
(function_declaration
|
||||
name: (identifier) @declaration.name) @declaration.function
|
||||
|
|
|
|||
|
|
@ -848,6 +848,20 @@ export const JAVASCRIPT_QUERIES = `
|
|||
(field_definition
|
||||
property: (property_identifier) @name) @definition.property
|
||||
|
||||
; Object-literal keys of a NAMED object (A1/A5). Idiomatic JS models config as
|
||||
; an object literal, not a class, so without these the fields of an options bag
|
||||
; have no node and "who reads/writes this setting?" answers a confident zero.
|
||||
;
|
||||
; Deliberately scoped to a literal BOUND TO A VARIABLE. An unbound literal is
|
||||
; usually an inline call argument or a JSX prop bag, whose keys are call-site
|
||||
; data rather than a named surface other code references — minting a node per
|
||||
; key there would add volume without adding an answerable question.
|
||||
(variable_declarator
|
||||
name: (identifier)
|
||||
value: (object
|
||||
(pair
|
||||
key: (property_identifier) @name) @definition.property))
|
||||
|
||||
; Closure-valued class fields (#2693) — see the TypeScript block for why these
|
||||
; are Method rather than Property.
|
||||
(field_definition
|
||||
|
|
|
|||
|
|
@ -15,27 +15,35 @@
|
|||
* that dominates idiomatic JS. Not precisely solvable without types;
|
||||
* covered by name-based fallback at reduced confidence.
|
||||
*
|
||||
* STATUS — not yet implemented; these are the acceptance criteria.
|
||||
* STATUS — definition nodes DONE, edge resolution REMAINING.
|
||||
*
|
||||
* Established so far:
|
||||
* - A parse-query pattern scoped to object literals BOUND TO A VARIABLE
|
||||
* (`(variable_declarator name: (identifier) value: (object (pair key:
|
||||
* (property_identifier) @name) @definition.property))`) matches correctly:
|
||||
* verified against the raw JAVASCRIPT_QUERIES, 4 captures on this fixture
|
||||
* with the right names. It is NOT enough on its own — no `Property` node
|
||||
* reaches the graph, and `local-symbol-pruner` is not the cause (it drops
|
||||
* only Const/Variable/Static). The remaining gate is in the parse worker's
|
||||
* node-creation path for `@definition.property` captures.
|
||||
* - The two receiver shapes need different mechanisms. Through the holding
|
||||
* variable the receiver is typeable and must resolve precisely. Through an
|
||||
* untyped param it is not, and needs name-based matching — sanctioned for
|
||||
* dynamic languages here (`fieldFallbackOnMethodLookup` defaults on, and
|
||||
* the Vue provider documents it recovering plain-object-literal cases) but
|
||||
* it must carry reduced confidence so precision is not overclaimed.
|
||||
* Done: object-literal keys bound to a variable now mint both halves — the
|
||||
* graph `Property` node (JAVASCRIPT_QUERIES) and the scope-resolution def
|
||||
* (languages/javascript/query.ts). A config field is findable by name where it
|
||||
* previously did not exist as a symbol at all.
|
||||
*
|
||||
* Remaining: the ACCESSES edges. The two receiver shapes need different
|
||||
* mechanisms and neither is implemented:
|
||||
* - Through the holding variable (`exitRules.exitMinAtrMult`) the receiver is
|
||||
* typeable, so it must resolve precisely — the Const holding the literal
|
||||
* has to be typed to the literal's scope, the way `classScopeByDefId` maps
|
||||
* a class def to its scope.
|
||||
* - Through an untyped param (`cfg.exitMinAtrMult`) it is not typeable in
|
||||
* plain JS and needs name-based matching — sanctioned for dynamic languages
|
||||
* here (`fieldFallbackOnMethodLookup` defaults on, and the Vue provider
|
||||
* documents it recovering plain-object-literal cases) but it must carry
|
||||
* reduced confidence so precision is not overclaimed.
|
||||
*
|
||||
* TRAP, learned the hard way and recorded so the next reader does not repeat
|
||||
* it: under vitest the PARSE WORKER runs the BUILT `dist/` code, because
|
||||
* `parse-impl.ts` resolves `../workers/parse-worker.js`, which does not exist
|
||||
* under `src/`, and falls back to dist. Scope resolution runs from `src`. So a
|
||||
* change to TYPESCRIPT/JAVASCRIPT_QUERIES is invisible to tests until
|
||||
* `npm run build` — it reads exactly like a failed hypothesis.
|
||||
*/
|
||||
import { describe, it, beforeAll } from 'vitest';
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import path from 'path';
|
||||
import { FIXTURES, runPipelineFromRepo, type PipelineResult } from './helpers.js';
|
||||
import { FIXTURES, getRelationships, runPipelineFromRepo, type PipelineResult } from './helpers.js';
|
||||
|
||||
describe('JavaScript plain-object property access (A1/A5)', () => {
|
||||
let result: PipelineResult;
|
||||
|
|
@ -47,8 +55,39 @@ describe('JavaScript plain-object property access (A1/A5)', () => {
|
|||
);
|
||||
}, 60000);
|
||||
|
||||
it.todo('indexes object-literal keys as Property nodes');
|
||||
const propertyNames = (): string[] =>
|
||||
Array.from(
|
||||
(result as unknown as { graph: { iterNodes(): Iterable<PropNode> } }).graph.iterNodes(),
|
||||
)
|
||||
.filter((n) => n.label === 'Property')
|
||||
.map((n) => String(n.properties.name));
|
||||
|
||||
const readersOf = (field: string): string[] =>
|
||||
getRelationships(result, 'ACCESSES')
|
||||
.filter((e) => e.target === field)
|
||||
.map((e) => e.source);
|
||||
|
||||
it('indexes object-literal keys as Property nodes', () => {
|
||||
const props = propertyNames();
|
||||
expect(props).toContain('exitMinAtrMult');
|
||||
expect(props).toContain('stopAtrMult');
|
||||
});
|
||||
|
||||
it('gives every indexed key a distinct node, not one merged symbol', () => {
|
||||
const props = propertyNames().filter((n) => n === 'exitMinAtrMult' || n === 'stopAtrMult');
|
||||
expect(new Set(props).size).toBe(2);
|
||||
});
|
||||
|
||||
// Blocked on receiver resolution — see STATUS above. `readersOf` is the
|
||||
// assertion these become once the receiver can be typed to the literal.
|
||||
it.todo('emits ACCESSES for a read through the holding variable');
|
||||
it.todo('emits ACCESSES for the property WRITE (A5)');
|
||||
it.todo('emits ACCESSES for a read through an untyped param (option bag)');
|
||||
|
||||
void readersOf;
|
||||
});
|
||||
|
||||
interface PropNode {
|
||||
readonly label: string;
|
||||
readonly properties: Record<string, unknown>;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue