Found while writing flaglint.dev documentation for cross-package factory-function resolution (#14/ADR 004): resolveReceiver (internal/scanner/identity.go) only handles a method call's receiver expression when it's an *ast.Ident or *ast.SelectorExpr — never an *ast.CallExpr. So a direct chained call on a factory function's result is not detected, only the assign-then-use form:
// NOT detected — receiver is a CallExpr (flags.Client()), not an Ident/SelectorExpr
flags.Client().BoolVariation("checkout-v2", ctx, false)
// Detected — the call result is first assigned to a variable
client := flags.Client()
client.BoolVariation("checkout-v2", ctx, false)
Confirmed via a minimal repro: identical code, only the call shape differs, and flaglint-go audit finds 0 usages for the chained form and 2 for the assigned form.
This affects factory-function resolution generally (any receiver expression that's itself a call — not just the specific cross-package factory case #14 added), and is idiomatic enough Go style (GetClient().Method()) to be worth fixing rather than leaving as a silent gap: resolveReceiver would need a new case for *ast.CallExpr that recognizes isSDKConstructorCall/isFactoryCall on the inner call and, if it resolves to a bound client version, treats the outer method call as detected directly (no binding-map lookup needed, since the version is known immediately from the inner call itself — no receiver variable to look up in bindings at all in this shape).
Not found during the original field-testing round (all 4 real repos' factory-getter usage happened to go through an intermediate variable) — found only while writing accurate documentation. Filed as a known gap; see docs/adr/004-whole-scan-identity-resolution.md for the surrounding factory-function design.
Found while writing flaglint.dev documentation for cross-package factory-function resolution (#14/ADR 004):
resolveReceiver(internal/scanner/identity.go) only handles a method call's receiver expression when it's an*ast.Identor*ast.SelectorExpr— never an*ast.CallExpr. So a direct chained call on a factory function's result is not detected, only the assign-then-use form:Confirmed via a minimal repro: identical code, only the call shape differs, and
flaglint-go auditfinds 0 usages for the chained form and 2 for the assigned form.This affects factory-function resolution generally (any receiver expression that's itself a call — not just the specific cross-package factory case #14 added), and is idiomatic enough Go style (
GetClient().Method()) to be worth fixing rather than leaving as a silent gap:resolveReceiverwould need a new case for*ast.CallExprthat recognizesisSDKConstructorCall/isFactoryCallon the inner call and, if it resolves to a bound client version, treats the outer method call as detected directly (no binding-map lookup needed, since the version is known immediately from the inner call itself — no receiver variable to look up inbindingsat all in this shape).Not found during the original field-testing round (all 4 real repos' factory-getter usage happened to go through an intermediate variable) — found only while writing accurate documentation. Filed as a known gap; see docs/adr/004-whole-scan-identity-resolution.md for the surrounding factory-function design.