From a440782cfac11c084ae6c585da66cb8e06e368e7 Mon Sep 17 00:00:00 2001 From: Krishan Kant Sharma Date: Wed, 8 Jul 2026 08:53:43 -0500 Subject: [PATCH] =?UTF-8?q?docs:=20refresh=20flaglint-go=20pages=20?= =?UTF-8?q?=E2=80=94=206=20listed=20gaps=20are=20resolved,=20add=20--stric?= =?UTF-8?q?t-types=20and=20baseline=20counts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every identity-resolution gap listed as "not resolved yet" on the flaglint-go Limitations and Supported Scope pages has actually been closed (verified directly against the flaglint-go issue tracker): chained factory-call-then-method (#20), method values (#6), interface satisfaction (#15), block-scoped shadowing (#5), factory returning a wrapper type (#16), and nested go.mod files (#17). These pages were last updated right before that work landed. - limitations.md / supported-scope.md: mark all six as resolved (noting which layer resolves each — Phase 1 default vs. opt-in --strict-types), add two newer Phase-1 mechanisms these pages never mentioned at all (a struct field declared *ld.LDClient with no observed construction, and a composite literal directly initializing a package-level var), and state plainly that zero identity-resolution gaps are currently tracked. - identity-model.md: add worked examples for both newer Phase-1 mechanisms above, plus a new "Phase 2 — --strict-types" section (interface satisfaction, transitive factory wrapping, cross-function method-value forwarding) that didn't exist on this page at all before. - index.md: same corrections to the "what flaglint-go does/doesn't do" summary — it previously named method values and interface satisfaction as open gaps. - cli/{scan,audit,validate}.md: --strict-types wasn't documented as a flag anywhere on the site despite being available on all three commands since it shipped; added to each page's options table. audit.md's baseline JSON example was also missing the "schemaVersion" and "counts" fields flaglint-go now emits; validate.md's Baseline Mode section gains a note on what "counts" actually catches (a brand-new duplicate of an already-baselined call, not just a brand-new fingerprint). Verified: `npm run build` completes cleanly (90 pages), spot-checked the rendered Limitations page's output directly. Signed-off-by: Krishan Kant Sharma --- src/content/docs/docs/go/cli/audit.md | 14 ++++- src/content/docs/docs/go/cli/scan.md | 3 +- src/content/docs/docs/go/cli/validate.md | 13 +++- .../docs/docs/go/concepts/identity-model.md | 63 ++++++++++++++++++- src/content/docs/docs/go/index.md | 10 +-- .../docs/docs/go/reference/limitations.md | 24 ++++--- .../docs/docs/go/reference/supported-scope.md | 31 +++++---- 7 files changed, 126 insertions(+), 32 deletions(-) diff --git a/src/content/docs/docs/go/cli/audit.md b/src/content/docs/docs/go/cli/audit.md index fe7c8bc..d57fe54 100644 --- a/src/content/docs/docs/go/cli/audit.md +++ b/src/content/docs/docs/go/cli/audit.md @@ -1,7 +1,7 @@ --- title: flaglint-go audit description: Risk-ranked LaunchDarkly Go SDK usage report with a migration-readiness score. -lastUpdated: 2026-07-06 +lastUpdated: 2026-07-08 --- `flaglint-go audit` scans your source and classifies every detected LaunchDarkly Go SDK call by risk level, producing a migration-readiness score. It never modifies files. @@ -20,6 +20,7 @@ flaglint-go audit [dir] [flags] | `-o, --output string` | Write report to a file instead of stdout | | `--config string` | Path to config file | | `--write-baseline string` | Write current finding fingerprints to a baseline file for use with `validate --baseline --fail-on-new` | +| `--strict-types` | Additionally resolve findings only provable with real `go/types` information (interface satisfaction, transitive factory wrapping, cross-function method-value forwarding). Requires the module to build. See [Identity Model](/docs/go/concepts/identity-model/). | ## Risk Levels @@ -62,15 +63,22 @@ flaglint-go audit ./services --write-baseline .flaglint-baseline.json ```json { "version": "1", - "createdAt": "2026-07-06T19:23:09Z", + "schemaVersion": "baseline.v1", + "createdAt": "2026-07-08T19:23:09Z", "flaglintVersion": "dev", "fingerprints": [ "launchdarkly:BoolVariation:checkout-v2:checkout.go", "launchdarkly:IntVariation:discount-percentage:checkout.go" - ] + ], + "counts": { + "launchdarkly:BoolVariation:checkout-v2:checkout.go": 1, + "launchdarkly:IntVariation:discount-percentage:checkout.go": 1 + } } ``` +`counts` records each fingerprint's occurrence count, not just which fingerprints are known — so `validate --fail-on-new` also catches a brand-new *duplicate* of an already-baselined call, not only a brand-new fingerprint. See [validate: Baseline Mode](/docs/go/cli/validate/#baseline-mode). + Commit this file to source control, then use it with `flaglint-go validate --baseline --fail-on-new` to adopt CI enforcement without requiring all existing debt to be fixed first. See [Quickstart: Adopt Gradually With a Baseline](/docs/go/quickstart/#4-adopt-gradually-with-a-baseline). ## Exit Behavior diff --git a/src/content/docs/docs/go/cli/scan.md b/src/content/docs/docs/go/cli/scan.md index 0f8f452..f1ce6ce 100644 --- a/src/content/docs/docs/go/cli/scan.md +++ b/src/content/docs/docs/go/cli/scan.md @@ -1,7 +1,7 @@ --- title: flaglint-go scan description: Structured, file-level LaunchDarkly Go SDK usage inventory. Always exits 0. -lastUpdated: 2026-07-06 +lastUpdated: 2026-07-08 --- `flaglint-go scan` walks a directory and reports every detected LaunchDarkly Go SDK call site as a structured inventory — no risk scoring, no readiness score, just the raw findings. Use `audit` instead when you want a risk-ranked summary. @@ -19,6 +19,7 @@ flaglint-go scan [dir] [flags] | `-f, --format string` | Output format: `json` \| `markdown` (default `markdown`) | | `-o, --output string` | Write report to a file instead of stdout | | `--config string` | Path to config file | +| `--strict-types` | Additionally resolve findings only provable with real `go/types` information (interface satisfaction, transitive factory wrapping, cross-function method-value forwarding). Requires the module to build. See [Identity Model](/docs/go/concepts/identity-model/). | ## Example Output (Markdown) diff --git a/src/content/docs/docs/go/cli/validate.md b/src/content/docs/docs/go/cli/validate.md index 944e2fb..7ae7eb4 100644 --- a/src/content/docs/docs/go/cli/validate.md +++ b/src/content/docs/docs/go/cli/validate.md @@ -1,7 +1,7 @@ --- title: flaglint-go validate description: Enforce a no-direct-LaunchDarkly policy, baseline-aware CI enforcement, and SARIF findings for Go. -lastUpdated: 2026-07-06 +lastUpdated: 2026-07-08 --- `flaglint-go validate` checks whether a Go codebase complies with a flag-usage policy. It's the only flaglint-go command that ever exits `1`. @@ -17,6 +17,7 @@ lastUpdated: 2026-07-06 | `--config string` | Path to config file | | `--baseline string` | Baseline file for comparing against known debt | | `--fail-on-new` | Exit `1` if any findings are not in the baseline | +| `--strict-types` | Additionally resolve findings only provable with real `go/types` information (interface satisfaction, transitive factory wrapping, cross-function method-value forwarding). Requires the module to build. See [Identity Model](/docs/go/concepts/identity-model/). | ## Blocking Policy Command @@ -64,6 +65,16 @@ Error: 1 new finding(s) not in baseline: Re-run `--write-baseline` whenever you accept new debt on purpose. +A baseline also tracks each finding's *occurrence count*, not just which fingerprints are known — so a brand-new **duplicate** of an already-baselined call (copy-pasted into the same file) is caught too, even though its fingerprint is already in the baseline: + +```text +Scanned 2 file(s). Found 3 LaunchDarkly Go SDK usage(s). +Error: 1 new finding(s) not in baseline: + - launchdarkly:BoolVariation:checkout-v2:checkout.go +``` + +A baseline written by an older version of flaglint-go (or by flaglint-js, which doesn't have this extension yet) works the same as always — falling back to plain fingerprint-set comparison when a baseline has no occurrence counts recorded. + ## SARIF ```bash diff --git a/src/content/docs/docs/go/concepts/identity-model.md b/src/content/docs/docs/go/concepts/identity-model.md index a5690e5..f6fa0c0 100644 --- a/src/content/docs/docs/go/concepts/identity-model.md +++ b/src/content/docs/docs/go/concepts/identity-model.md @@ -1,7 +1,7 @@ --- title: flaglint-go Identity Model description: How flaglint-go proves a variable is a LaunchDarkly client — never by name, and across the whole scan, not just one file. -lastUpdated: 2026-07-06 +lastUpdated: 2026-07-08 --- flaglint-go's core rule, inherited from flaglint-js: a variable is only treated as a LaunchDarkly client when its identity can be **proven** — never by matching a method or variable name in isolation. @@ -68,9 +68,68 @@ func useClient(client *ld.LDClient) { } ``` +**Struct fields declared `*ld.LDClient`, with no observed construction anywhere in the scanned tree** — the dominant Go dependency-injection pattern: the client is wired into the struct by an external framework or a constructor not included in what's actually scanned. The field's declared type alone is sufficient proof, the same "trust the signature" principle as parameter-typed bindings above, just applied to a struct field instead: +```go +type FlagService struct { + ld *ld.LDClient +} + +func (s *FlagService) DarkMode(userID string) bool { + v, _ := s.ld.BoolVariation("dark-mode", ldcontext.New(userID), false) // bound from the field's declared type alone + return v +} +``` + +**Composite literals directly initializing a package-level `var`**, never inside any function body: +```go +var svcClient, _ = ld.MakeClient("sdk-key", 5*time.Second) +var svc = &Svc{Client: svcClient} // never inside a function — resolved at the package level + +func NewUI(userID string) bool { + v, _ := svc.Client.BoolVariation("new-ui", ldcontext.New(userID), false) + return v +} +``` + +## Phase 2 — `--strict-types` + +Phase 1's syntactic tracing is structurally unable to prove a small number of patterns — they need real type information, not just syntax. An opt-in `--strict-types` pass loads the target module with real `go/types` information (via `golang.org/x/tools/go/packages`) and uses it to resolve these additionally. This pass is strictly *additive*: it can only add findings Phase 1 missed, never remove or contradict one Phase 1 already made. Phase 1 remains the default — `flaglint-go audit` continues to work unconditionally, with no build required; `--strict-types` requires the scanned module to actually build. + +**Interface satisfaction** — a client known only through an interface type, not the concrete `*ld.LDClient` type itself: +```go +type FlagEvaluator interface { + BoolVariation(key string, ctx ldcontext.Context, defaultVal bool) (bool, error) +} + +func useEvaluator(e FlagEvaluator) { + e.BoolVariation("my-flag", nil, false) // e's declared type is an interface, not *ld.LDClient — + // requires go/types to confirm *ld.LDClient satisfies it +} +``` + +**A factory function returning a wrapper type**, not `*ld.LDClient` itself (transitive factory wrapping): +```go +func NewClientWrapper() *ClientWrapper { /* constructs and wraps a real client */ } + +wrapper := NewClientWrapper() +wrapper.inner.BoolVariation("my-flag", nil, false) +``` + +**A method value passed as an argument into a *different* function** (the "forwarding function" pattern) — the harder remainder beyond the same-function method-value case Phase 1 already resolves: +```go +func callDirect(f func(string, ldcontext.Context, bool) (bool, error), key string, def bool) bool { + v, _ := f(key, nil, def) + return v +} + +func useDirect(client *ld.LDClient) { + _ = callDirect(client.BoolVariation, "forwarding-flag", false) // method value crosses a function boundary +} +``` + ## What stays out of scope -Identity resolution is deliberately conservative — flaglint-go prefers a missed detection (false negative) over a false positive. Patterns not resolved without real type information (`go/types`) are documented, not silently guessed at. See [Limitations](/docs/go/reference/limitations/) for the current list. +Identity resolution is deliberately conservative — flaglint-go prefers a missed detection (false negative) over a false positive. Patterns not resolved by either Phase 1 or `--strict-types` are documented, not silently guessed at. See [Limitations](/docs/go/reference/limitations/) for the current list. ## Dynamic Key Detection diff --git a/src/content/docs/docs/go/index.md b/src/content/docs/docs/go/index.md index d83acc2..a591ce1 100644 --- a/src/content/docs/docs/go/index.md +++ b/src/content/docs/docs/go/index.md @@ -1,7 +1,7 @@ --- title: flaglint-go Overview description: Audit LaunchDarkly Go server SDK usage with a native Go binary — no Node.js required. -lastUpdated: 2026-07-06 +lastUpdated: 2026-07-08 tableOfContents: false --- @@ -12,7 +12,7 @@ brew install flaglint/tap/flaglint-go flaglint-go audit ./services ``` -No API key. No source upload. Runs locally against your checkout, using Go's own `go/parser`/`go/ast` — no build required, no `go/types` type-checking, no tree-sitter. +No API key. No source upload. Runs locally against your checkout, using Go's own `go/parser`/`go/ast` by default — no build required, no `go/types` type-checking, no tree-sitter. An opt-in `--strict-types` pass is available for the small number of patterns pure syntax can't resolve on its own; see [Identity Model](/docs/go/concepts/identity-model/).
Quickstart @@ -36,15 +36,15 @@ flaglint-go is an early-access, separate binary — not a Go mode bolted onto th ## What flaglint-go Does -- Performs local, syntax-only source analysis — no build, no `go/types`, no network access. +- Performs local source analysis, syntax-only by default (no build, no `go/types`, no network access), with an opt-in `--strict-types` pass for the few patterns pure syntax can't resolve. - Detects `github.com/launchdarkly/go-server-sdk` v6 and v7 evaluation calls, proven through import-alias tracing and constructor-call binding — never through name matching alone. -- Resolves common real-world indirection across an entire scan (not just one file): struct fields, composite literals, multi-level field chains, cross-package factory/getter functions, and typed parameters. See [Identity Model](/docs/go/concepts/identity-model/). +- Resolves common real-world indirection across an entire scan (not just one file): struct fields (including a field's declared type alone, with no construction observed anywhere), composite literals (including one that directly initializes a package-level `var`), multi-level field chains, cross-package factory/getter functions, and typed parameters. See [Identity Model](/docs/go/concepts/identity-model/). - Generates the same inventory/audit/validate reports as flaglint-js (JSON, Markdown, text, SARIF). ## What flaglint-go Does Not Do - It does not rewrite code. There is no `migrate`/`--apply` command yet. -- It does not resolve every indirection pattern — method values, interface satisfaction, and a few other gaps are documented and tracked. See [Limitations](/docs/go/reference/limitations/). +- It does not resolve every indirection pattern. What's left is documented and tracked as it's found — currently there are no open identity-resolution gaps. See [Limitations](/docs/go/reference/limitations/) for the current list. - It does not query LaunchDarkly for flag age, owner, evaluation history, or production usage. ## Feedback diff --git a/src/content/docs/docs/go/reference/limitations.md b/src/content/docs/docs/go/reference/limitations.md index a77af89..61bec6b 100644 --- a/src/content/docs/docs/go/reference/limitations.md +++ b/src/content/docs/docs/go/reference/limitations.md @@ -1,7 +1,7 @@ --- title: flaglint-go Limitations description: Current boundaries and non-goals of flaglint-go. -lastUpdated: 2026-07-06 +lastUpdated: 2026-07-08 --- flaglint-go is intentionally narrow and honest about what it can't yet prove. @@ -12,16 +12,22 @@ There is no `migrate`/`--apply` command. flaglint-go audits and enforces policy; ## Identity Resolution Gaps -flaglint-go proves client identity from syntax alone — no build, no `go/types`. That means a few real patterns aren't resolved yet, all filed as tracked, documented gaps rather than silently guessed at: +flaglint-go proves client identity in two layers: Phase 1 (the default `audit`/`scan`/`validate` behavior) resolves purely from syntax — no build, no `go/types`. An opt-in `--strict-types` pass (Phase 2) additionally loads the target module with real `go/types` information to resolve a small number of patterns pure syntax structurally can't — see [Identity Model](/docs/go/concepts/identity-model/) for the split and why it's opt-in (it requires the module to build). -- **Chained factory-call-then-method** (`pkg.GetLdClient().Method(...)` with no intermediate variable) — [issue #20](https://github.com/flaglint/flaglint-go/issues/20) -- **Method values** (`f := client.BoolVariation; f(...)`) — [issue #6](https://github.com/flaglint/flaglint-go/issues/6) -- **Interface satisfaction** (a client known only through an interface type) — [issue #15](https://github.com/flaglint/flaglint-go/issues/15) -- **Block-scoped variable shadowing** within a single function — [issue #5](https://github.com/flaglint/flaglint-go/issues/5). Unlike every other gap below, this one **can cause a false positive**: a variable re-`:=`'d to an unrelated value inside a nested block is still treated as the outer real client. -- **A factory function returning a wrapper type**, not `*ld.LDClient` itself — [issue #16](https://github.com/flaglint/flaglint-go/issues/16) -- **Nested `go.mod` files** within one scanned tree (monorepo submodules) — [issue #17](https://github.com/flaglint/flaglint-go/issues/17) +As of 2026-07-08, there are no identity-resolution gaps currently tracked against flaglint-go — every gap previously documented on this page has been resolved. New gaps are filed as found; see the [issue tracker](https://github.com/flaglint/flaglint-go/issues) for the current list. -Every gap above fails safe (a missed detection, never a false positive) except block-scoped shadowing, noted above. See [Identity Model](/docs/go/concepts/identity-model/) and [ADR 004](https://github.com/flaglint/flaglint-go/blob/main/docs/adr/004-whole-scan-identity-resolution.md) for the full design and reasoning behind each. +Recently closed, kept here for continuity: + +- **Chained factory-call-then-method** (`pkg.GetLdClient().Method(...)` with no intermediate variable) — [issue #20](https://github.com/flaglint/flaglint-go/issues/20), resolved in Phase 1. +- **Method values within one function** (`f := client.BoolVariation; f(...)`) — [issue #6](https://github.com/flaglint/flaglint-go/issues/6), resolved in Phase 1. The harder cross-function case (a method value passed as an argument into a different function) — [issue #26](https://github.com/flaglint/flaglint-go/issues/26) — is resolved under `--strict-types`. +- **Interface satisfaction** (a client known only through an interface type) — [issue #15](https://github.com/flaglint/flaglint-go/issues/15), resolved under `--strict-types`. +- **Block-scoped variable shadowing** within a single function — [issue #5](https://github.com/flaglint/flaglint-go/issues/5), resolved in Phase 1. This was the one gap that could cause a false positive rather than a missed detection. +- **A factory function returning a wrapper type**, not `*ld.LDClient` itself — [issue #16](https://github.com/flaglint/flaglint-go/issues/16), resolved under `--strict-types`. +- **Nested `go.mod` files** within one scanned tree (monorepo submodules) — [issue #17](https://github.com/flaglint/flaglint-go/issues/17), resolved in Phase 1. +- **A struct field declared `*ld.LDClient` with no observed construction anywhere in the scanned tree** (the dominant Go dependency-injection pattern) — [issue #32](https://github.com/flaglint/flaglint-go/issues/32), resolved in Phase 1. +- **A composite literal directly initializing a package-level `var`** (`var svc = &Svc{Client: svcClient}`, never inside any function body) — [issue #33](https://github.com/flaglint/flaglint-go/issues/33), resolved in Phase 1. + +Every gap above failed safe (a missed detection, never a false positive) except block-scoped shadowing, noted above. See [Identity Model](/docs/go/concepts/identity-model/) and [ADR 004](https://github.com/flaglint/flaglint-go/blob/main/docs/adr/004-whole-scan-identity-resolution.md) for the full design and reasoning behind each. ## Not Production Staleness Analysis diff --git a/src/content/docs/docs/go/reference/supported-scope.md b/src/content/docs/docs/go/reference/supported-scope.md index 54d9473..10c174c 100644 --- a/src/content/docs/docs/go/reference/supported-scope.md +++ b/src/content/docs/docs/go/reference/supported-scope.md @@ -1,7 +1,7 @@ --- title: flaglint-go Supported Scope description: What flaglint-go detects, reports, and excludes. -lastUpdated: 2026-07-06 +lastUpdated: 2026-07-08 --- This page covers **flaglint-go**, the Go CLI. For the JavaScript/TypeScript CLI's scope, see [flaglint-js: Supported Scope](/docs/reference/supported-scope/). @@ -26,24 +26,33 @@ flaglint-go detects LaunchDarkly Go server-side SDK evaluation calls from: ## Identity Resolution Coverage -flaglint-go proves client identity syntactically (no `go/types`, no build required) across an entire scan. See [Identity Model](/docs/go/concepts/identity-model/) for detail on each of these: +flaglint-go proves client identity in two layers. Phase 1 (the default `audit`/`scan`/`validate` behavior, always on) resolves purely from syntax — no `go/types`, no build required. An opt-in `--strict-types` pass (Phase 2) additionally resolves a small number of patterns pure syntax structurally can't. See [Identity Model](/docs/go/concepts/identity-model/) for the full split and detail on each of these. + +### Phase 1 — default, no build required | Pattern | Resolved | | --- | --- | | Direct constructor binding (`x := ld.MakeClient(...)`) | Yes | | Package-level `var` and struct-field assignment | Yes — across the whole scan, not just one file | -| Composite-literal struct-field binding (`&Integration{ldClient: client}`) | Yes | +| Composite-literal struct-field binding (`&Integration{ldClient: client}`) | Yes — including a literal that directly initializes a package-level `var`, never inside any function body | | Multi-level field-selector chains (`f.integ.ldClient.Method(...)`), including generic structs | Yes | | Cross-package factory/getter functions (`pkg.GetLdClient()`) | Yes — requires a `go.mod` to compute real import paths | | Parameter-typed client bindings (`func f(client *ld.LDClient)`) | Yes | -| Chained factory-call-then-method (`pkg.GetLdClient().Method(...)`, no intermediate variable) | **No** — [tracked](https://github.com/flaglint/flaglint-go/issues/20) | -| Method values (`f := client.BoolVariation; f(...)`) | No — [tracked](https://github.com/flaglint/flaglint-go/issues/6) | -| Interface satisfaction (client known only through an interface type) | No — [tracked](https://github.com/flaglint/flaglint-go/issues/15) | -| Block-scoped variable shadowing within one function | No — can cause a false positive, see below — [tracked](https://github.com/flaglint/flaglint-go/issues/5) | -| A factory function returning a wrapper type (not `*ld.LDClient` itself) | No — [tracked](https://github.com/flaglint/flaglint-go/issues/16) | -| Nested `go.mod` files within one scanned tree (monorepo submodules) | Partial — [tracked](https://github.com/flaglint/flaglint-go/issues/17) | - -Every "No" above is a false-negative risk except one: block-scoped shadowing (issue #5) can cause a genuine false positive — a variable re-`:=`'d to an unrelated value inside a nested block is still treated as the outer real client. Every other gap fails safe (a missed detection, never a false positive) — flaglint-go's non-negotiable rule is to under-detect rather than guess everywhere else. See [Limitations](/docs/go/reference/limitations/). +| A struct field declared `*ld.LDClient`, with no observed construction anywhere in the scanned tree | Yes — the field's declared type alone is sufficient proof, the dominant Go dependency-injection pattern | +| Chained factory-call-then-method (`pkg.GetLdClient().Method(...)`, no intermediate variable) | Yes | +| Method values within one function (`f := client.BoolVariation; f(...)`) | Yes | +| Block-scoped variable shadowing within one function | Yes — no longer a false-positive risk | +| Nested `go.mod` files within one scanned tree (monorepo submodules) | Yes | + +### Phase 2 — `--strict-types` (opt-in, requires the module to build) + +| Pattern | Resolved | +| --- | --- | +| Interface satisfaction (client known only through an interface type) | Yes | +| A factory function returning a wrapper type (not `*ld.LDClient` itself) | Yes | +| A method value passed as an argument into a *different* function (the "forwarding function" pattern) | Yes | + +Every pattern above fails safe when it can't be resolved (a missed detection, never a false positive) — flaglint-go's non-negotiable rule is to under-detect rather than guess. See [Limitations](/docs/go/reference/limitations/) for anything currently outside detection coverage. ## Feedback