mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-07 02:58:02 +00:00
feat(typescript): index type aliases and shape members as symbols
A TS frontend models its API contracts as `type X = { … }` and `interface`,
so a field on one is exactly what "who breaks if I remove this?" is asked
about. Three gaps made that unanswerable, all in the TypeScript queries:
1. No `type_alias_declaration` -> `@definition.type`, so an alias minted NO
NODE AT ALL and a context() lookup on an exported contract type answered
"Symbol not found". TypeScript was the ONLY language missing this — Rust
(type_item), Kotlin (type_alias), Swift (typealias_declaration) and Dart
all emit it. The alias was declared for scope resolution but never became
a graph symbol.
2. No `property_signature` in the parse query, so INTERFACE members minted no
Property nodes either — the upstream report's "class/interface index fine"
holds only for the type, not its fields.
3. No `property_signature` in the scope query, so even with nodes present the
resolver had no member declaration to aim at. Its sibling
`method_signature` -> `@declaration.method` already existed; only
properties were missing.
Interface bodies and object-type aliases both spell members as
property_signature, so one pattern per query covers both shapes.
Lands the SYMBOLS, not yet the ACCESSES edges: the shape is already a
class-like scope and now has member declarations, but no edge forms — the
remaining link is owner/type-binding, recorded as todos with the diagnosis.
Note TypeScript sets fieldFallbackOnMethodLookup:false, so unlike JavaScript
there is deliberately no name-based fallback here; the precise path is the
only route by design.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
841a62ae36
commit
541ab122de
4 changed files with 124 additions and 0 deletions
|
|
@ -498,6 +498,16 @@ export const TYPESCRIPT_SCOPE_QUERY = `
|
|||
(method_signature
|
||||
name: (property_identifier) @declaration.name) @declaration.method
|
||||
|
||||
;; Members of a declared SHAPE — interface bodies and object-type aliases both
|
||||
;; spell them as property_signature (A4). The sibling method_signature rule
|
||||
;; above declared interface METHODS, so only properties were missing: a typed
|
||||
;; receiver resolved to the shape's scope and then found no member there, and
|
||||
;; the field's consumers were unreachable. TypeScript sets
|
||||
;; fieldFallbackOnMethodLookup:false, so there is no name-based safety net
|
||||
;; here — the precise path is the only one, and it needs the declaration.
|
||||
(property_signature
|
||||
name: (property_identifier) @declaration.name) @declaration.property
|
||||
|
||||
;; Declarations — class fields
|
||||
(public_field_definition
|
||||
name: (property_identifier) @declaration.name) @declaration.property
|
||||
|
|
|
|||
|
|
@ -24,6 +24,21 @@ export const TYPESCRIPT_QUERIES = `
|
|||
(interface_declaration
|
||||
name: (type_identifier) @name) @definition.interface
|
||||
|
||||
; Type aliases (A4). TypeScript was the only language whose aliases minted no
|
||||
; node: Rust (type_item), Kotlin (type_alias), Swift (typealias_declaration)
|
||||
; and Dart all emit @definition.type. The alias was declared for scope
|
||||
; resolution but never became a graph symbol, so a context() lookup on an
|
||||
; exported API-contract type answered "Symbol not found".
|
||||
(type_alias_declaration
|
||||
name: (type_identifier) @name) @definition.type
|
||||
|
||||
; Members of a declared SHAPE — interface bodies and object-type aliases both
|
||||
; spell them as property_signature, so one pattern covers both. A TS frontend
|
||||
; models its API contracts this way, and without these there is no graph path
|
||||
; from a contract field to the code that reads it.
|
||||
(property_signature
|
||||
name: (property_identifier) @name) @definition.property
|
||||
|
||||
(function_declaration
|
||||
name: (identifier) @name) @definition.function
|
||||
|
||||
|
|
|
|||
19
gitnexus/test/fixtures/lang-resolution/typescript-alias-fields/contracts.ts
vendored
Normal file
19
gitnexus/test/fixtures/lang-resolution/typescript-alias-fields/contracts.ts
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// A4: API contracts modelled as type aliases and interfaces — the common style
|
||||
// in a TS frontend. Neither the alias node nor the members of either shape were
|
||||
// indexed, so there was no graph path from a field to its consumers.
|
||||
export type LiveModeConfig = {
|
||||
bookSlots: number;
|
||||
bookNotionalUsdt: number;
|
||||
};
|
||||
|
||||
export interface LiveModeIface {
|
||||
ifaceSlots: number;
|
||||
}
|
||||
|
||||
export function renderAlias(cfg: LiveModeConfig): number {
|
||||
return cfg.bookNotionalUsdt + cfg.bookSlots;
|
||||
}
|
||||
|
||||
export function renderIface(cfg: LiveModeIface): number {
|
||||
return cfg.ifaceSlots;
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* A4 — TypeScript type aliases and interface members must be indexed.
|
||||
*
|
||||
* A TS frontend models its API contracts as `type X = { … }` and `interface`,
|
||||
* so a field on one is the thing you ask "who breaks if I remove this?" about.
|
||||
* Three gaps made that unanswerable, all in the TypeScript PARSE query:
|
||||
*
|
||||
* 1. No `type_alias_declaration` -> `@definition.type`, so the alias minted
|
||||
* NO NODE AT ALL and `context({name:'LiveModeConfig'})` said "Symbol not
|
||||
* found". TypeScript was the only language missing this — Rust
|
||||
* (`type_item`), Kotlin (`type_alias`), Swift (`typealias_declaration`)
|
||||
* and Dart all emit it.
|
||||
* 2. No `property_signature` pattern, so INTERFACE members minted no
|
||||
* `Property` nodes either — the upstream report's "class/interface index
|
||||
* fine" is only half right.
|
||||
* 3. Alias members likewise had no node.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import { FIXTURES, getRelationships, runPipelineFromRepo, type PipelineResult } from './helpers.js';
|
||||
import path from 'path';
|
||||
|
||||
interface LabelledNode {
|
||||
readonly label: string;
|
||||
readonly properties: Record<string, unknown>;
|
||||
}
|
||||
|
||||
describe('TypeScript type-alias and interface members (A4)', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'typescript-alias-fields'), () => {});
|
||||
}, 60000);
|
||||
|
||||
const nodesOfLabel = (label: string): string[] =>
|
||||
Array.from(
|
||||
(result as unknown as { graph: { iterNodes(): Iterable<LabelledNode> } }).graph.iterNodes(),
|
||||
)
|
||||
.filter((n) => n.label === label)
|
||||
.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 the type alias as a symbol', () => {
|
||||
// Previously "Symbol not found" — the alias existed for scope resolution
|
||||
// but never became a graph node.
|
||||
expect(nodesOfLabel('TypeAlias')).toContain('LiveModeConfig');
|
||||
});
|
||||
|
||||
it('indexes type-alias members as Property nodes', () => {
|
||||
const props = nodesOfLabel('Property');
|
||||
expect(props).toContain('bookNotionalUsdt');
|
||||
expect(props).toContain('bookSlots');
|
||||
});
|
||||
|
||||
it('indexes interface members as Property nodes', () => {
|
||||
expect(nodesOfLabel('Property')).toContain('ifaceSlots');
|
||||
});
|
||||
|
||||
// The EDGES are not landed yet — nodes and declarations are.
|
||||
//
|
||||
// Established: the shape is a class-like scope already (`interface_declaration`
|
||||
// and `type_alias_declaration value:(object_type)` both emit `@scope.class`),
|
||||
// and `property_signature` now emits `@declaration.property` alongside the
|
||||
// pre-existing `method_signature` -> `@declaration.method`. So the receiver
|
||||
// has a scope and the scope has members, yet no ACCESSES forms — the missing
|
||||
// link is owner/type-binding, i.e. the member def carrying an `ownerId` that
|
||||
// the typed receiver resolves to via `findOwnedMember`.
|
||||
//
|
||||
// There is deliberately NO name-based safety net here: TypeScript sets
|
||||
// `fieldFallbackOnMethodLookup: false` (scope-resolver.ts) because name
|
||||
// matching over-connects in a typed language, and the unique-name pass
|
||||
// honors that opt-out. The precise path is the only route for TS, by design.
|
||||
it.todo('links an alias field to its consumer');
|
||||
it.todo('links an interface field to its consumer');
|
||||
|
||||
void readersOf;
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue