Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/content/docs/docs/go/cli/audit.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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 <file> --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
Expand Down
3 changes: 2 additions & 1 deletion src/content/docs/docs/go/cli/scan.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)

Expand Down
13 changes: 12 additions & 1 deletion src/content/docs/docs/go/cli/validate.md
Original file line number Diff line number Diff line change
@@ -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`.
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
63 changes: 61 additions & 2 deletions src/content/docs/docs/go/concepts/identity-model.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions src/content/docs/docs/go/index.md
Original file line number Diff line number Diff line change
@@ -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
---

Expand All @@ -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/).

<div class="button-grid">
<a href="/docs/go/quickstart">Quickstart</a>
Expand All @@ -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
Expand Down
24 changes: 15 additions & 9 deletions src/content/docs/docs/go/reference/limitations.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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

Expand Down
Loading
Loading