Aalexbit-codemod

csharp-service-call-mining

Mine direct HTTP service-to-service calls in C# (HttpClient.* and JSON helpers); read-only metrics that surface caller→callee edges, transport details, URL provenance, and per-file auth presence

miningmetricscsharpservice-architecturehttpclientdependency-map
Public
1 executions

Run locally

npx codemod csharp-service-call-mining

csharp-service-call-mining

Read-only mining codemod that maps direct HTTP service-to-service calls in a
C# codebase. It emits one metric row per call site so a service-architecture
cleanup starts from code-level truth — caller class → callee, with call-site
count, transport details, URL provenance, and per-file auth presence — instead
of a high-level diagram somebody drew last quarter.

What it detects

HttpClient-shaped calls of the form <receiver>.<Method>Async(...):

  • GetAsync, PostAsync, PutAsync, DeleteAsync, PatchAsync,
    SendAsync
  • GetStringAsync, GetByteArrayAsync, GetStreamAsync
  • GetFromJsonAsync<T>, PostAsJsonAsync, PutAsJsonAsync, PatchAsJsonAsync

The match is structural (kind-based on invocation_expression
member_access_expression → method name), so the receiver does not have
to be named httpClient. Real callers like _httpClient, this._http, or
_apiClient are all captured; the receiver text is reported on the row.

What it ignores

  • Generated files (*.Designer.cs, *.g.cs, *.g.i.cs)
  • bin/, obj/, Generated/ directories
  • SendAsync(HttpRequestMessage) is recorded but the URL gets
    urlKind=request-object since the URL lives inside the request object, not
    the call site

The codemod is strictly read-only: it returns null and never edits
source. It is safe to run repeatedly during a cleanup to track progress.

Output

For every match, the codemod emits a csharp-service-http-calls metric row
with these fields:

fieldmeaning
sourceServiceEnclosing C# type name (class / record / struct / interface)
sourceNamespaceEnclosing namespace (block-scoped or file-scoped)
receiverText of the call's receiver expression (e.g. _httpClient, this._http)
methodThe exact method name (GetAsync, PostAsJsonAsync, …)
httpMethodDerived verb: GET / POST / PUT / PATCH / DELETE / ANY
urlKindliteral-absolute, literal-relative, interpolated-absolute, interpolated-relative, config, concatenation, variable, request-object, or expression
targetHostHost pulled from absolute URLs (literal or interpolated leading text)
targetServiceFirst non-empty path segment, or the config key when the URL comes from configuration
urlConfigKeyThe key/property used when urlKind=config (e.g. "CustomerService:BaseUrl")
argCountArgument count (helps spot payload variation across call sites)
fileAuthconfigured if the file mentions any well-known auth surface (Authorization literal, DefaultRequestHeaders.Authorization, AuthenticationHeaderValue, SetBearerToken, …); absent otherwise
fileRelative path of the C# file
line1-based line number of the call site

Aggregating those rows in Codemod Insight (or jq) gives you the dependency
map the Notion brief asks for:

text

Usage

Run against a checkout:

bash

Run only the codemod (no workflow):

bash

Test:

bash

Heuristics & caveats

  • targetHost requires either a literal absolute URL or an interpolated
    string whose leading text contains the host. Configured / variable URLs
    surface as urlKind=config / variable with the relevant key in
    urlConfigKey.
  • targetService is best-effort: for absolute URLs it is the first path
    segment (/customers/{id}customers); for configured URLs it falls back
    to the config key (EmailService:BaseUrl); for SendAsync it is empty.
  • fileAuth is per file, not per call site. It does not validate scopes,
    token lifetimes, or refresh logic. It only flags files where authorization
    is wired somewhere, so cross-checking each fileAuth=absent row is part
    of the manual triage.
  • The codemod assumes ast-grep / tree-sitter understands the C# in the file.
    Generated, partial, or heavily preprocessed files may yield ERROR nodes
    that are silently skipped.

Why this is the right first step

The Notion brief
(How we'd modernize a tangled C# service architecture)
spells it out: boundary problems beat language problems, and you can't fix
boundaries you can't see. Start with the call-graph, prove the edges with
code, then introduce contracts on the worst offenders. This codemod gives you
the call-graph in a deterministic, repeatable way — no guessing, no
hand-drawn diagrams.

Ready to contribute?

Build your own codemod and share it with the community.