ek9_query_call_graph
Answers first: callerNames / calleeNames and callerSites / calleeSites ('file.ek9:LINE') - then the chain to the requested depth.
When to use it
Who calls a method or function, and what does it call, to a depth - one call with symbol:'Type.member', no file needed.
When not to use it
- Every reference to a symbol (types, fields, not just method calls) — use
ek9_query_references(symbol-level usage, not call chains). - Analysis WITHIN one body — use
ek9_query_flowfor control flow (branches, loops), orek9_query_pipelinefor stage-level stream-pipeline analysis. - All callable members of a TYPE, not one method's calls — use
ek9_query_effective_api. - Details of one symbol at a cursor rather than call chains — use
ek9_inspect_symbol_at_cursor.
Details
direction: 'both' (default) | 'outgoing' (callees, what this depends on) | 'incoming' (callers, refactor-impact); depth 0-5 (default 1, capped at 5), with depth>1 recursing via a nested children[] per node — hop level is implicit in nesting. Response: the headline names and sites, the target's flat signature/complexity fields, then incomingCalls[]/outgoingCalls[]; each node carries caller or callee (display name), fullyQualifiedName, calledAt 'file.ek9:LINE' (the call expression; a caller calling more than once has its first here and the rest in alsoCalledAt[]), declaredAt 'file.ek9:LINE' (the callable's declaration), callKind (function-call | method-call), pure and context (the source line at the call site). The graph is built during PRE_IR_CHECKS — a workspace compiled to an earlier phase errors with the reached phase (the default ek9_compile phase suffices), and a line not on a method/function returns {callGraph:'not_found', message:...}.
Arguments
| Argument | Type | Required | What it is |
|---|---|---|---|
among | array of string | Names to answer FOR: 'which of these ...' - the name lists shrink to these and amongUnmatched names the ones that are not in the answer. Bare names, 'mod::Type' or 'Type.member'; a MODULE name stands for every construct in it ('which in scale.m07 do not ...' = amongUnmatched). | |
depth | integer | Maximum traversal depth (0-5, default 1) | |
direction | string: both | outgoing | incoming | Direction: 'both' (default), 'outgoing', or 'incoming' | |
file | string | The EK9 source file - OPTIONAL when symbol is a construct name; required to anchor on a member or a use site. | |
line | integer | 1-based line of the declaration or a reference - OMIT it and pass symbol:'<name>' to let the tool find the line (a model counting lines in a file miscounts); if you do count, use ek9_read_file(lineNumbers:true). | |
symbol | string | A construct NAME alone ('Shape', 'sample.deps.core::Validator') - no file needed, the tool finds it workspace-wide; or, with file (+ line), the identifier you mean there. |
It also takes the arguments every tool takes.
Recorded calls
Real calls, recorded against small projects and checked on every build - a changed answer fails it. 3 of the 7 recorded are shown.
Program by symbol
Request
{
"direction" : "outgoing",
"symbol" : "Main"
}
Response
{
"calleeNames" : [ "greet" ],
"calleeSites" : [ "Hello.ek9:13" ],
"callerNames" : [ ],
"callerSites" : [ ],
"depth" : 1,
"direction" : "outgoing",
"enclosingGenus" : "PROGRAM",
"enclosingType" : "Main",
"fullyQualifiedName" : "com.example.hello::Main",
"meta" : {
"serverMs" : "<timing>"
},
"method" : "Main",
"outgoingCallCount" : 1,
"outgoingCalls" : [ {
"callKind" : "function-call",
"calledAt" : "Hello.ek9:13",
"callee" : "greet",
"context" : "greet(\"World\")",
"declaredAt" : "Hello.ek9:6",
"fullyQualifiedName" : "com.example.hello::greet",
"pure" : false
} ],
...
Cut short here: 5 more lines. The whole recorded call.
Call graph shape
Request
{
"file" : "Hello.ek9",
"line" : 13
}
Response
{
"calleeNames" : [ "greet" ],
"calleeSites" : [ "Hello.ek9:13" ],
"callerNames" : [ ],
"callerSites" : [ ],
"depth" : 1,
"direction" : "both",
"enclosingGenus" : "PROGRAM",
"enclosingType" : "Main",
"fullyQualifiedName" : "com.example.hello::Main",
"incomingCallCount" : 0,
"incomingCalls" : [ ],
"meta" : {
"serverMs" : "<timing>"
},
"method" : "Main",
"outgoingCallCount" : 1,
"outgoingCalls" : [ {
"callKind" : "function-call",
"calledAt" : "Hello.ek9:13",
"callee" : "greet",
"context" : "greet(\"World\")",
"declaredAt" : "Hello.ek9:6",
"fullyQualifiedName" : "com.example.hello::greet",
...
Cut short here: 7 more lines. The whole recorded call.
Overload named by parameterisation
Request
{
"direction" : "incoming",
"symbol" : "Stash.keep(List of Integer)"
}
Response
{
"calleeNames" : [ ],
"calleeSites" : [ ],
"callerNames" : [ "Tally" ],
"callerSites" : [ "Stash.ek9:32" ],
"cognitiveComplexity" : 0,
"complexity" : 1,
"depth" : 1,
"direction" : "incoming",
"enclosingGenus" : "CLASS",
"enclosingType" : "Stash",
"fullyQualifiedName" : "com.example.stash::keep",
"incomingCallCount" : 1,
"incomingCalls" : [ {
"callKind" : "method-call",
"calledAt" : "Stash.ek9:32",
"caller" : "Tally",
"context" : "stash.keep(items)",
"declaredAt" : "Stash.ek9:28",
"fullyQualifiedName" : "com.example.stash::Tally",
"pure" : false
} ],
"meta" : {
"serverMs" : "<timing>"
...
Cut short here: 12 more lines. The whole recorded call.