mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-09 03:17:54 +00:00
fix(ingestion): class-field closures are callable members in TS/JS (#2693)
A CALLS edge must target a callable node. `class A { handler = (x) => x }` emitted
a Property, so calling it produced `CALLS -> Property:A.ts:A.handler` — an edge
pointing at something the graph says is not callable. Same defect class as the
JS/TS `var` binding fixed in the previous commit, and the last place a closure
binding still carried a value label.
Kotlin already models its class-body closure as Method + HAS_METHOD; TS/JS now
match, so all three agree:
class-field closure -> Method + HAS_METHOD (CALLS target is callable)
plain class field -> Property + HAS_PROPERTY (unchanged, no CALLS)
Anchored on public_field_definition / field_definition — the same nodes the
property rules use — so the parse-worker dedup collapses the pair rather than
leaving a Method/Property twin, the failure the Java rule hit in the previous
commit.
ON MATCHING THE COMPILERS. This deliberately diverges from tsc and SCIP. The
TypeScript compiler classes `handler = () => {}` as a PropertyDeclaration
("a property declaration independently from what it's assigned to"), and SCIP
gives it a `.` term descriptor, the same suffix as any field — both call it a
property, and Kotlin's compiler likewise treats `val f = { }` as a property with
a function type. The divergence is intentional: GitNexus's Function/Method label
does not mean "tsc SymbolFlags", it means "this node can be the target of a
CALLS edge", which is the convention #2687 set for closure bindings in every
language. Modelling it the compiler's way would mean either dropping call
resolution for these members or emitting a separate node for the lambda and
flowing the property to it — the two-node shape #2687 removed. Recorded here so
the next reader does not "fix" it back.
Tests: TS and JS class-field arrows resolve to their Method node, plus a guard
that a NON-closure class field stays a Property — the closure rule must key on
the initializer, not on the field syntax.
This commit is contained in:
parent
6ba8c52a20
commit
5645d6592c
2 changed files with 63 additions and 0 deletions
|
|
@ -342,6 +342,25 @@ export const TYPESCRIPT_QUERIES = `
|
|||
(public_field_definition
|
||||
name: (private_property_identifier) @name) @definition.property
|
||||
|
||||
; Closure-valued class fields (#2693): \`handler = (x) => x\` is a CALLABLE
|
||||
; member, so it emits Method like every other closure binding rather than a
|
||||
; Property that CALLS edges would point at — a call target must be callable.
|
||||
; Kotlin already models its class-body closure this way (Method + HAS_METHOD).
|
||||
;
|
||||
; Note this diverges from tsc's SymbolFlags and SCIP's descriptor, which both
|
||||
; class an arrow-initialized field as a PROPERTY/term. That is deliberate: the
|
||||
; label here means "is a call target", not "is a tsc symbol kind", and #2687 set
|
||||
; that convention for closure bindings in every language. Anchored on
|
||||
; public_field_definition — the same node the property rules use — so the
|
||||
; parse-worker dedup collapses the pair (callable ranks highest).
|
||||
(public_field_definition
|
||||
name: (property_identifier) @name
|
||||
value: (arrow_function)) @definition.method
|
||||
|
||||
(public_field_definition
|
||||
name: (property_identifier) @name
|
||||
value: (function_expression)) @definition.method
|
||||
|
||||
; Constructor parameter properties: constructor(public address: Address)
|
||||
(required_parameter
|
||||
(accessibility_modifier)
|
||||
|
|
@ -667,6 +686,16 @@ export const JAVASCRIPT_QUERIES = `
|
|||
(field_definition
|
||||
property: (property_identifier) @name) @definition.property
|
||||
|
||||
; Closure-valued class fields (#2693) — see the TypeScript block for why these
|
||||
; are Method rather than Property.
|
||||
(field_definition
|
||||
property: (property_identifier) @name
|
||||
value: (arrow_function)) @definition.method
|
||||
|
||||
(field_definition
|
||||
property: (property_identifier) @name
|
||||
value: (function_expression)) @definition.method
|
||||
|
||||
; Write access: obj.field = value
|
||||
(assignment_expression
|
||||
left: (member_expression
|
||||
|
|
|
|||
|
|
@ -116,6 +116,14 @@ describe('closure bindings emit a single Function node in every language', () =>
|
|||
]);
|
||||
});
|
||||
|
||||
it('TypeScript: a NON-closure class field stays a Property', async () => {
|
||||
// The closure rule must key on the initializer, not the field syntax —
|
||||
// otherwise every class field would become a callable member.
|
||||
expect(
|
||||
await labelsFor('src/plain.ts', 'export class A {\n address = "x";\n}\n', 'address'),
|
||||
).toEqual(['Property']);
|
||||
});
|
||||
|
||||
it('Python: an annotated attribute stays a Property, not a Variable', async () => {
|
||||
// Regression guard. Python matches BOTH `@definition.property` (annotated)
|
||||
// and `@definition.variable` (bare assignment) on the same statement at the
|
||||
|
|
@ -405,6 +413,32 @@ describeIfWorkerBuilt('closure bindings resolve in the remaining languages (#269
|
|||
expect(targets).toEqual(['Function:b.php:handler']);
|
||||
});
|
||||
|
||||
it('TypeScript: a class-field arrow is a callable member, like Kotlin', async () => {
|
||||
// A CALLS edge must target a CALLABLE node. This field used to emit
|
||||
// Property, so the edge pointed at a non-callable — the same defect class
|
||||
// as the `var` case below. Kotlin already modelled its class-body closure
|
||||
// as Method + HAS_METHOD.
|
||||
//
|
||||
// This diverges from tsc (PropertyDeclaration) and SCIP (a `.` term), both
|
||||
// of which class an arrow-initialised field as a property. Deliberate: the
|
||||
// label means "is a call target" here, not "is a tsc symbol kind".
|
||||
const targets = await callTargetsFor(
|
||||
'Box.ts',
|
||||
'export class Box {\n handler = (x: number) => x;\n caller(): number { return this.handler(1); }\n}\n',
|
||||
);
|
||||
|
||||
expect(targets).toEqual(['Method:Box.ts:Box.handler']);
|
||||
});
|
||||
|
||||
it('JavaScript: a class-field arrow is a callable member', async () => {
|
||||
const targets = await callTargetsFor(
|
||||
'C.js',
|
||||
'export class C {\n handler = (x) => x;\n caller() { return this.handler(1); }\n}\n',
|
||||
);
|
||||
|
||||
expect(targets).toEqual(['Method:C.js:C.handler']);
|
||||
});
|
||||
|
||||
it('JavaScript: a `var` closure binding is a Function, like const/let', async () => {
|
||||
// `var` is a different grammar node than const/let, so it kept a Variable
|
||||
// label — and the CALLS edge that resolved through the declaration route
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue