Summary
In TS/JS, new Foo() emits no edge, in the same file or across files, even though
new_expression is listed in _TS_CONFIG.call_types. Static calls and plain function
calls in the same method body resolve normally, so walk_calls itself is fine — only the
construction site is dropped.
Same user-visible symptom as #2997 (C#, fixed in #2998) and the PHP gap I just filed
(#3115), but a different root cause: here the node type is dispatched, its callee field is
just never read.
Repro (graphify 0.9.50, tree_sitter_typescript 0.23.2)
One file, graphify extract . --code-only:
export class Foo { constructor(public x: number) {} static make(): Foo { return new Foo(0) } }
export function helper(n: number): number { return n + 1 }
export class Caller {
run(): void {
const a = new Foo(1) // new
const b = Foo.make() // static method call
const c = helper(2) // plain function call
}
}
Edges emitted from Caller.run (excluding contains/method):
| call site |
edge |
new Foo(1) |
none |
Foo.make() |
run --calls--> Foo.make |
helper(2) |
run --calls--> helper |
Cross-file behaves identically: with Foo in models.ts and a named
import { Foo } from './models', the import edge is emitted, but new Foo(1) in the
method body still produces nothing. For contrast, the equivalent Python file
(a = Foo(1)) emits both calls and uses — instantiation is a call there, so the
generic path covers it.
Likely root cause
In tree-sitter-typescript the two node types expose the callee under different field names:
new_expression : function=None constructor=identifier arguments=arguments
call_expression : function=identifier constructor=None arguments=arguments
_TS_CONFIG (and _TSX_CONFIG, which inherits it) sets:
call_types=frozenset({"call_expression", "new_expression"}),
call_function_field="function",
So child_by_field_name("function") returns None for every new_expression, no callee
name is extracted, and the entry in call_types is effectively inert. #708 added
new_expression extraction, but the field name looks like it was never adjusted.
C# solves the equivalent problem with a node-type special case in walk_calls
(engine.py, elif config.ts_module == "tree_sitter_c_sharp" and node.type == "object_creation_expression"). A per-node-type callee field, or the same kind of special
case reading constructor, would cover TS/JS/TSX.
Why it matters
"What breaks if I change X" is the main reason to query a graph instead of grepping, and in
class-based TS a type that is only ever constructed at its call sites now looks unused.
Summary
In TS/JS,
new Foo()emits no edge, in the same file or across files, even thoughnew_expressionis listed in_TS_CONFIG.call_types. Static calls and plain functioncalls in the same method body resolve normally, so
walk_callsitself is fine — only theconstruction site is dropped.
Same user-visible symptom as #2997 (C#, fixed in #2998) and the PHP gap I just filed
(#3115), but a different root cause: here the node type is dispatched, its callee field is
just never read.
Repro (graphify 0.9.50, tree_sitter_typescript 0.23.2)
One file,
graphify extract . --code-only:Edges emitted from
Caller.run(excludingcontains/method):new Foo(1)Foo.make()run --calls--> Foo.makehelper(2)run --calls--> helperCross-file behaves identically: with
Fooinmodels.tsand a namedimport { Foo } from './models', the import edge is emitted, butnew Foo(1)in themethod body still produces nothing. For contrast, the equivalent Python file
(
a = Foo(1)) emits bothcallsanduses— instantiation is a call there, so thegeneric path covers it.
Likely root cause
In tree-sitter-typescript the two node types expose the callee under different field names:
_TS_CONFIG(and_TSX_CONFIG, which inherits it) sets:So
child_by_field_name("function")returnsNonefor everynew_expression, no calleename is extracted, and the entry in
call_typesis effectively inert. #708 addednew_expressionextraction, but the field name looks like it was never adjusted.C# solves the equivalent problem with a node-type special case in
walk_calls(
engine.py,elif config.ts_module == "tree_sitter_c_sharp" and node.type == "object_creation_expression"). A per-node-type callee field, or the same kind of specialcase reading
constructor, would cover TS/JS/TSX.Why it matters
"What breaks if I change X" is the main reason to query a graph instead of grepping, and in
class-based TS a type that is only ever constructed at its call sites now looks unused.