From 79794b3d15fb351d583e1b3122b36fb89e52f889 Mon Sep 17 00:00:00 2001 From: Krishan Kant Sharma Date: Mon, 6 Jul 2026 14:19:49 -0500 Subject: [PATCH 1/3] docs: fix stale claims that Go is unsupported/roadmap-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit flaglint-go shipped (v0.1.0/v0.1.1) as a separate, real CLI months after these pages were written — they still said Go SDK usage was entirely unsupported/roadmap-only via a tree-sitter-go plan that was never built. - supported-scope.md: split the "Go, Java, Python..." row; Go now points to flaglint-go's own supported-scope page instead of being lumped in with genuinely-unsupported languages. - safety-model.md: clarified this page is specifically about flaglint-js's migrate --apply (which flaglint-go doesn't have), not a blanket "Go is unsupported by FlagLint" statement. - changelog.md: updated the ADR 006 (Go language support, PROPOSED) entry to note it was superseded — Go support shipped as flaglint-go, built on native go/parser instead of the originally-proposed tree-sitter-go. Links to the new /docs/go/ section (landing in a follow-up PR) — the actual Go docs content doesn't exist yet, so those links 404 until that PR merges. Signed-off-by: Krishan Kant Sharma --- src/content/docs/docs/concepts/safety-model.md | 7 +++++-- src/content/docs/docs/reference/changelog.md | 8 ++++++-- src/content/docs/docs/reference/supported-scope.md | 7 +++++-- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/content/docs/docs/concepts/safety-model.md b/src/content/docs/docs/concepts/safety-model.md index 2602cb1..2f21c69 100644 --- a/src/content/docs/docs/concepts/safety-model.md +++ b/src/content/docs/docs/concepts/safety-model.md @@ -1,9 +1,11 @@ --- title: Safety Model description: What FlagLint auto-rewrites, what it skips, and why the safety-over-coverage principle guides every pattern. -lastUpdated: 2026-06-22 +lastUpdated: 2026-07-06 --- +This page covers **flaglint-js**'s `migrate --apply` rewrite safety model, which is specific to the JavaScript/TypeScript CLI — flaglint-go does not have an automated migration/rewrite command (audit, scan, and validate only). See [flaglint-go's concepts docs](/docs/go/concepts/identity-model/) for the Go CLI's own safety guarantees. + FlagLint is conservative by design. It separates inventory, review, and source edits so teams can inspect every migration change before it lands in production. ## Safety Model Diagram @@ -45,7 +47,8 @@ When every condition is met, FlagLint produces an idempotent, argument-order-cor | **Wrappers not configured in `.flaglintrc`** | FlagLint cannot distinguish a custom wrapper from an unrelated function unless it is explicitly declared | Add wrapper declarations to `wrappers: []` in `.flaglintrc`, then re-run | | **Ambiguous fallback types** — e.g. `null`, a variable, or a union-typed expression | FlagLint cannot determine which `get*Value` method to use without a concrete literal type | Replace the fallback with an explicit literal (`false`, `""`, `0`, `{}`) at the call site | | **Non-LaunchDarkly providers** | FlagLint is a LaunchDarkly-specific static analysis tool | No action; these calls are reported in the inventory but never touched | -| **Non-Node.js SDKs** — browser SDK, Go SDK, Java SDK | Only the Node.js server-side SDK import provenance is verified | Migrate browser and other-language calls manually | +| **Non-Node.js SDKs** — browser SDK, Java SDK | Only the Node.js server-side SDK import provenance is verified | Migrate browser and other-language calls manually | +| **Go SDK** | flaglint-js only verifies the Node.js server-side SDK import — Go usage is out of scope for this tool entirely | Use [flaglint-go](/docs/go/) to audit Go usage; it has no `--apply`-style rewrite yet, only `scan`/`audit`/`validate` | ## Why Fallback Value Does Not Equal Production Value diff --git a/src/content/docs/docs/reference/changelog.md b/src/content/docs/docs/reference/changelog.md index 180bbe3..fa1b1da 100644 --- a/src/content/docs/docs/reference/changelog.md +++ b/src/content/docs/docs/reference/changelog.md @@ -75,8 +75,12 @@ v1.0.0 is the CI-readiness release. The core scanner and migrator are stable. Th - **ADR 005 — Cleanup command** (DEFERRED) — documents the safe path for automated branch removal via `--assume`/`--flag-values`; deferred until `--assume` mechanism is designed. -- **ADR 006 — Go language support** (PROPOSED) — tree-sitter-go parser design, - import tracing for `go-server-sdk`, go.mod boundary handling, 6-phase plan. +- **ADR 006 — Go language support** (PROPOSED, later superseded) — + tree-sitter-go parser design, import tracing for `go-server-sdk`, go.mod + boundary handling, 6-phase plan. Go support ultimately shipped as a + separate project, [flaglint-go](https://github.com/flaglint/flaglint-go), + built around Go's native `go/parser`/`go/ast` instead of tree-sitter — see + [flaglint-go's docs](/docs/go/). ### Fixed diff --git a/src/content/docs/docs/reference/supported-scope.md b/src/content/docs/docs/reference/supported-scope.md index f905014..f9b0f1b 100644 --- a/src/content/docs/docs/reference/supported-scope.md +++ b/src/content/docs/docs/reference/supported-scope.md @@ -1,9 +1,11 @@ --- title: Supported Scope description: What FlagLint detects, migrates, reports for manual review, and excludes. -lastUpdated: 2026-07-01 +lastUpdated: 2026-07-06 --- +This page covers **flaglint-js**, the JavaScript/TypeScript CLI. For the Go CLI's supported scope, see [flaglint-go: Supported Scope](/docs/go/reference/supported-scope/). + FlagLint currently supports LaunchDarkly Node.js server-side SDK evaluation calls in JavaScript and TypeScript from: - `@launchdarkly/node-server-sdk` @@ -30,7 +32,8 @@ React SDK hooks, HOC, and provider are detected and reported for manual review. | React SDK hooks (`useFlags`, `useLDClient`, `useVariation`) | Yes | No | Yes | No | | React SDK HOC (`withLDConsumer`) | Yes | No | Yes | No | | React SDK provider (`LDProvider`, `asyncWithLDProvider`) | Yes | No | Yes | No | -| Go, Java, Python, or other SDKs | No | No | No | Yes | +| Go SDK | Handled by the separate [flaglint-go](/docs/go/) CLI | — | — | — | +| Java, Python, or other SDKs | No | No | No | Yes | | Non-LaunchDarkly providers | No | No | No | Yes | ## Supported Import Provenance From 563572ad394b7d4e11f7ab7af4de6a7b9ec190dc Mon Sep 17 00:00:00 2001 From: Krishan Kant Sharma Date: Mon, 6 Jul 2026 14:29:12 -0500 Subject: [PATCH 2/3] docs: add flaglint-go documentation section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New "Go CLI" sidebar section, parallel to the existing JS docs tree rather than interleaved with it (Tutorials/Guides/Integrations stay JS-specific — flaglint-go doesn't have equivalents yet): - Overview (docs/go) — what it is, how it compares to flaglint-js - Quickstart — install, audit/scan/validate walkthrough, baseline adoption - CLI reference — scan, audit, validate (flaglint-go has no migrate/init/ completion yet, so no reference pages for those) - Identity Model — the whole-scan resolution story (struct fields, composite literals, multi-level chains incl. generics, cross-package factory functions, parameter-typed bindings), distilled from ADR 002/004 - Supported Scope / Limitations — every capability and every documented gap, linking directly to the real tracked GitHub issues (#5, #6, #15-17, #20) All example output is real, captured by running the actual flaglint-go binary against a small demo project — not hand-written. Verified in the browser via the Astro dev server (sidebar, code blocks, tables, and cross-links all render correctly), and `npm run build` passes clean. Found one new scanner gap while building this: a direct chained factory call (`pkg.GetLdClient().Method(...)`, no intermediate variable) isn't detected, only the assign-then-use form is — filed as flaglint-go#20 and documented in the Supported Scope table rather than papered over in the example code. Signed-off-by: Krishan Kant Sharma --- astro.config.mjs | 14 ++ src/content/docs/docs/go/cli/audit.md | 84 ++++++++++++ src/content/docs/docs/go/cli/scan.md | 74 ++++++++++ src/content/docs/docs/go/cli/validate.md | 99 ++++++++++++++ .../docs/docs/go/concepts/identity-model.md | 83 +++++++++++ src/content/docs/docs/go/index.md | 53 +++++++ src/content/docs/docs/go/quickstart.md | 129 ++++++++++++++++++ .../docs/docs/go/reference/limitations.md | 40 ++++++ .../docs/docs/go/reference/supported-scope.md | 52 +++++++ 9 files changed, 628 insertions(+) create mode 100644 src/content/docs/docs/go/cli/audit.md create mode 100644 src/content/docs/docs/go/cli/scan.md create mode 100644 src/content/docs/docs/go/cli/validate.md create mode 100644 src/content/docs/docs/go/concepts/identity-model.md create mode 100644 src/content/docs/docs/go/index.md create mode 100644 src/content/docs/docs/go/quickstart.md create mode 100644 src/content/docs/docs/go/reference/limitations.md create mode 100644 src/content/docs/docs/go/reference/supported-scope.md diff --git a/astro.config.mjs b/astro.config.mjs index 19e559c..5671976 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -116,6 +116,20 @@ export default defineConfig({ { label: "OpenTelemetry", slug: "docs/integrations/opentelemetry" }, ], }, + { + label: "Go CLI", + collapsed: true, + items: [ + { label: "Overview", slug: "docs/go" }, + { label: "Quickstart", slug: "docs/go/quickstart" }, + { label: "scan", slug: "docs/go/cli/scan" }, + { label: "audit", slug: "docs/go/cli/audit" }, + { label: "validate", slug: "docs/go/cli/validate" }, + { label: "Identity Model", slug: "docs/go/concepts/identity-model" }, + { label: "Supported Scope", slug: "docs/go/reference/supported-scope" }, + { label: "Limitations", slug: "docs/go/reference/limitations" }, + ], + }, { label: "Trust", collapsed: true, diff --git a/src/content/docs/docs/go/cli/audit.md b/src/content/docs/docs/go/cli/audit.md new file mode 100644 index 0000000..fe7c8bc --- /dev/null +++ b/src/content/docs/docs/go/cli/audit.md @@ -0,0 +1,84 @@ +--- +title: flaglint-go audit +description: Risk-ranked LaunchDarkly Go SDK usage report with a migration-readiness score. +lastUpdated: 2026-07-06 +--- + +`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. + +## Command + +```bash +flaglint-go audit [dir] [flags] +``` + +## Options + +| Flag | Description | +| --- | --- | +| `-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 | +| `--write-baseline string` | Write current finding fingerprints to a baseline file for use with `validate --baseline --fail-on-new` | + +## Risk Levels + +| Method | Risk | Notes | +|---|---|---| +| `BoolVariation` / `StringVariation` / `IntVariation` / `Float64Variation` (and their `*Ctx` forms) | Low | Static key only | +| `JSONVariation` (and `*Ctx`) | Medium | Pointer output, manual review | +| `*VariationDetail(Ctx)` methods | High | Returns `EvaluationDetail` — no direct OpenFeature equivalent | +| `AllFlagsState` | High | Bulk call, no single-flag equivalent | +| Dynamic flag key (identifier, `fmt.Sprintf`, string concatenation) | High | Cannot resolve statically — overrides the method's own risk level | + +## Example Output + +```text +Scan complete — 2 unique flag(s) across 2 call site(s) (0ms, 2 file(s)) +Migration readiness: 100/100 · ready + 2 low risk · 0 medium risk · 0 high risk +``` + +A codebase with genuine high-risk usage: + +```text +Scan complete — 0 unique flag(s) across 4 call site(s) (2.3s, 4512 file(s)) +Migration readiness: 0/100 · complex + 0 low risk · 0 medium risk · 4 high risk +``` + +(All four call sites use a dynamic, per-instance flag key — correctly classified high risk regardless of the underlying method, per the dynamic-key rule above.) + +## Migration Readiness + +The score is the ratio of low/medium-risk (safely reviewable) call sites to total detected call sites, expressed 0–100. `N/A` is reported when zero direct calls are found — there's nothing to score. + +## Baseline Mode (`--write-baseline`) + +```bash +flaglint-go audit ./services --write-baseline .flaglint-baseline.json +``` + +```json +{ + "version": "1", + "createdAt": "2026-07-06T19:23:09Z", + "flaglintVersion": "dev", + "fingerprints": [ + "launchdarkly:BoolVariation:checkout-v2:checkout.go", + "launchdarkly:IntVariation:discount-percentage:checkout.go" + ] +} +``` + +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 + +`audit` always exits `0` unless the tool itself errors. It is informational — use `flaglint-go validate --no-direct-launchdarkly` to fail a build. + +## Feedback + +- [Edit this page on GitHub](https://github.com/flaglint/flaglint.dev/edit/main/src/content/docs/docs/go/cli/audit.md) +- [Report an issue](https://github.com/flaglint/flaglint-go/issues/new) +- Next: [validate](/docs/go/cli/validate/) diff --git a/src/content/docs/docs/go/cli/scan.md b/src/content/docs/docs/go/cli/scan.md new file mode 100644 index 0000000..0f8f452 --- /dev/null +++ b/src/content/docs/docs/go/cli/scan.md @@ -0,0 +1,74 @@ +--- +title: flaglint-go scan +description: Structured, file-level LaunchDarkly Go SDK usage inventory. Always exits 0. +lastUpdated: 2026-07-06 +--- + +`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. + +## Command + +```bash +flaglint-go scan [dir] [flags] +``` + +## Options + +| Flag | Description | +| --- | --- | +| `-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 | + +## Example Output (Markdown) + +```text +# FlagLint Go Scan Report + +Scanned 2 file(s) in 0ms — 2 unique flag(s) across 2 call site(s). + +## Flags + +| Flag | Call Type | Risk | File | +|---|---|---|---| +| `checkout-v2` | BoolVariation | low | checkout.go:13 | +| `discount-percentage` | IntVariation | low | checkout.go:20 | +``` + +## Example Output (JSON) + +```json +{ + "scannedFiles": 2, + "totalUsages": 2, + "uniqueFlags": ["checkout-v2", "discount-percentage"], + "usages": [ + { + "flagKey": "checkout-v2", + "isDynamic": false, + "file": "checkout.go", + "line": 13, + "column": 16, + "callType": "BoolVariation", + "fingerprint": "launchdarkly:BoolVariation:checkout-v2:checkout.go", + "stalenessSignals": [], + "language": "go", + "sdk": "go-server-sdk-v7", + "risk": "low" + } + ], + "warnings": [] +} +``` + +Fingerprints (`launchdarkly:callType:flagKey:normalizedPath[:dynamicIndex]`) are stable across line-number changes and match flaglint-js's format byte-for-byte — see the [cross-tool contract](https://github.com/flaglint/flaglint-go/blob/main/docs/adr/003-cross-tool-contract.md). + +## Exit Behavior + +`scan` always exits `0` unless the tool itself errors (invalid directory, bad `--format`, unreadable config). It never fails based on what it finds — use `flaglint-go validate` for a CI gate. + +## Feedback + +- [Edit this page on GitHub](https://github.com/flaglint/flaglint.dev/edit/main/src/content/docs/docs/go/cli/scan.md) +- [Report an issue](https://github.com/flaglint/flaglint-go/issues/new) +- Next: [audit](/docs/go/cli/audit/) diff --git a/src/content/docs/docs/go/cli/validate.md b/src/content/docs/docs/go/cli/validate.md new file mode 100644 index 0000000..944e2fb --- /dev/null +++ b/src/content/docs/docs/go/cli/validate.md @@ -0,0 +1,99 @@ +--- +title: flaglint-go validate +description: Enforce a no-direct-LaunchDarkly policy, baseline-aware CI enforcement, and SARIF findings for Go. +lastUpdated: 2026-07-06 +--- + +`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`. + +## Options + +| Flag | Description | +| --- | --- | +| `--no-direct-launchdarkly` | Fail if any direct LaunchDarkly Go SDK evaluation calls are found | +| `--bootstrap-exclude stringArray` | Glob pattern for files allowed to use the LaunchDarkly SDK directly (repeatable) | +| `-f, --format string` | Output format: `text` \| `sarif` (default `text`) | +| `-o, --output string` | Write report to a file instead of stdout | +| `--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 | + +## Blocking Policy Command + +```bash +flaglint-go validate ./services --no-direct-launchdarkly +``` + +```text +✗ validate --no-direct-launchdarkly: 2 direct LaunchDarkly Go SDK call(s) found. + + checkout.go:13:16 — BoolVariation("checkout-v2") + checkout.go:20:12 — IntVariation("discount-percentage") + +These call sites must migrate to OpenFeature before this rule passes. +``` + +Pass output once no direct calls remain: + +```text +✓ validate --no-direct-launchdarkly: no direct LaunchDarkly Go SDK calls found. + Scanned 2 file(s). +``` + +## Baseline Mode + +`--baseline`/`--fail-on-new` runs **independently** of `--no-direct-launchdarkly` — use it alone to adopt CI enforcement before existing debt is resolved: + +```bash +flaglint-go audit ./services --write-baseline .flaglint-baseline.json +flaglint-go validate ./services --baseline .flaglint-baseline.json --fail-on-new +``` + +```text +Scanned 2 file(s). Found 2 LaunchDarkly Go SDK usage(s). +✓ No new findings beyond baseline +``` + +Once new debt is introduced: + +```text +Scanned 2 file(s). Found 3 LaunchDarkly Go SDK usage(s). +Error: 1 new finding(s) not in baseline: + - launchdarkly:BoolVariation:new-feature-flag:checkout.go +``` + +Re-run `--write-baseline` whenever you accept new debt on purpose. + +## SARIF + +```bash +flaglint-go validate ./services --no-direct-launchdarkly --format sarif --output flaglint.sarif +``` + +SARIF findings use rule ID `flaglint.go.direct-launchdarkly` (Go-namespaced — distinct from flaglint-js's `flaglint.direct-launchdarkly`, per the [cross-tool contract](https://github.com/flaglint/flaglint-go/blob/main/docs/adr/003-cross-tool-contract.md)). Note: unlike flaglint-js's 0-based ESTree columns, Go's `token.Position.Column` is already 1-based, so flaglint-go does not add 1 to column numbers. + +## Bootstrap Exclusions + +Use `--bootstrap-exclude` for files allowed to use the SDK directly (a provider-wiring package, for example): + +```bash +flaglint-go validate ./services --no-direct-launchdarkly --bootstrap-exclude "internal/openfeature-bootstrap/**" +``` + +## Exit Codes + +| Code | Meaning | +| --- | --- | +| 0 | Success — no policy violations | +| 1 | Policy failure — direct calls found (`--no-direct-launchdarkly`) or new findings beyond baseline (`--fail-on-new`) | +| 2 | Invalid input — bad directory, bad `--format`, malformed config/baseline | +| 3 | Internal error | +| 130 | Interrupted (Ctrl-C) | + +`scan` and `audit` never exit anything but `0` (barring a tool error) — `validate` is the only policy gate. + +## Feedback + +- [Edit this page on GitHub](https://github.com/flaglint/flaglint.dev/edit/main/src/content/docs/docs/go/cli/validate.md) +- [Report an issue](https://github.com/flaglint/flaglint-go/issues/new) +- Next: [Identity Model](/docs/go/concepts/identity-model/) diff --git a/src/content/docs/docs/go/concepts/identity-model.md b/src/content/docs/docs/go/concepts/identity-model.md new file mode 100644 index 0000000..a5690e5 --- /dev/null +++ b/src/content/docs/docs/go/concepts/identity-model.md @@ -0,0 +1,83 @@ +--- +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 +--- + +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. + +## Why not just search for method names? + +Most flag scanners look for calls like `.BoolVariation(...)` or `.variation(...)` anywhere in the source. This produces false positives the moment an unrelated type happens to define a same-named method: + +```go +// ❌ a name-only scanner would match this +type FakeClient struct{} +func (c *FakeClient) BoolVariation(key string, ctx interface{}, def bool) (bool, error) { + return def, nil +} + +// ✅ flaglint-go correctly ignores it — FakeClient is never traced back +// to a github.com/launchdarkly/go-server-sdk import +``` + +flaglint-go proves identity through **import-alias tracing plus constructor-call binding**: a variable is only a client if it traces back, through the AST, to `ld.MakeClient(...)`/`ld.MakeCustomClient(...)` for a package actually imported from `github.com/launchdarkly/go-server-sdk` (v6 or v7) — whatever local alias is used, including dot-imports. + +## Whole-scan resolution + +Real-world Go code rarely constructs and uses the client in the same place. flaglint-go parses every file in a scan up front and resolves several indirection patterns *across* files — not just within one: + +**Struct fields and composite literals** — the client stored into a wrapper struct: +```go +type Integration struct { + ldClient *ld.LDClient +} + +func setup() *Integration { + client, _ := ld.MakeClient("sdk-key", 5*time.Second) + return &Integration{ldClient: client} +} +``` + +**Multi-level field chains**, including through generics — a struct's fields can be declared in a different file (even a different file than where they're used): +```go +type FeatureFlag[T comparable] struct { + integ *Integration +} + +func (f *FeatureFlag[T]) Evaluate() bool { + v, _ := f.integ.ldClient.BoolVariation("my-flag", nil, false) + return v +} +``` + +**Cross-package factory/getter functions** — a package-level singleton getter called from a different package, resolved via real `go.mod`-derived import paths (never a name-based guess): +```go +// package flags +func GetLdClient() *ld.LDClient { /* ... */ } + +// package main +client := flags.GetLdClient() +client.BoolVariation("my-flag", nil, false) +``` + +**Parameter-typed bindings** — a client passed in as a plain parameter, with no assignment to trace at all: +```go +func useClient(client *ld.LDClient) { + client.BoolVariation("my-flag", nil, false) // bound from the declared type alone +} +``` + +## 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. + +## Dynamic Key Detection + +A flag-key argument is `isDynamic: true` whenever it isn't a string literal — an identifier, a `fmt.Sprintf(...)` call, or string concatenation. Dynamic call sites are always classified high risk, regardless of the underlying method's own risk level, since the key "cannot resolve statically" no matter how simple the method otherwise is. + +## Feedback + +- [Edit this page on GitHub](https://github.com/flaglint/flaglint.dev/edit/main/src/content/docs/docs/go/concepts/identity-model.md) +- [Report an issue](https://github.com/flaglint/flaglint-go/issues/new) +- Next: [Supported Scope](/docs/go/reference/supported-scope/) diff --git a/src/content/docs/docs/go/index.md b/src/content/docs/docs/go/index.md new file mode 100644 index 0000000..11383be --- /dev/null +++ b/src/content/docs/docs/go/index.md @@ -0,0 +1,53 @@ +--- +title: flaglint-go Overview +description: Audit LaunchDarkly Go server SDK usage with a native Go binary — no Node.js required. +lastUpdated: 2026-07-06 +tableOfContents: false +--- + +flaglint-go is the Go-native counterpart to flaglint-js — a standalone binary for auditing [LaunchDarkly Go server SDK](https://github.com/launchdarkly/go-server-sdk) usage, built for teams that don't want a Node.js dependency in their toolchain. + +```bash +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. + + + +## flaglint-go vs. flaglint-js + +flaglint-go is an early-access, separate binary — not a Go mode bolted onto the npm CLI. They share the same fingerprint, exit-code, config, and SARIF conventions ([cross-tool contract](https://github.com/flaglint/flaglint-go/blob/main/docs/adr/003-cross-tool-contract.md)), but flaglint-go's command surface is currently narrower: + +| | flaglint-js | flaglint-go | +|---|---|---| +| `scan` — structured inventory | ✅ | ✅ | +| `audit` — risk-ranked report + readiness score | ✅ | ✅ | +| `validate` — CI policy gate | ✅ | ✅ | +| `migrate` — automated OpenFeature rewrites | ✅ | ❌ not yet | +| `init` / `completion` | ✅ | ❌ not yet | + +## What flaglint-go Does + +- Performs local, syntax-only source analysis — no build, no `go/types`, no network access. +- 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/). +- 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 query LaunchDarkly for flag age, owner, evaluation history, or production usage. + +## Feedback + +- [Edit this page on GitHub](https://github.com/flaglint/flaglint.dev/edit/main/src/content/docs/docs/go/index.md) +- [Report an issue](https://github.com/flaglint/flaglint-go/issues/new) +- Next: [Quickstart](/docs/go/quickstart/) diff --git a/src/content/docs/docs/go/quickstart.md b/src/content/docs/docs/go/quickstart.md new file mode 100644 index 0000000..7bcbaac --- /dev/null +++ b/src/content/docs/docs/go/quickstart.md @@ -0,0 +1,129 @@ +--- +title: flaglint-go Quickstart +description: Install flaglint-go, run your first audit, and enforce a CI policy — no Node.js required. +lastUpdated: 2026-07-06 +--- + +## Installation + +**Homebrew (macOS / Linux)** +```bash +brew install flaglint/tap/flaglint-go +``` + +**go install** +```bash +go install github.com/flaglint/flaglint-go/cmd/flaglint-go@latest +``` + +Prebuilt release binaries are also available from the [releases page](https://github.com/flaglint/flaglint-go/releases). + +## Requirements + +- A Go project using `github.com/launchdarkly/go-server-sdk` v6 or v7. +- Nothing else — flaglint-go is a single static binary with no runtime dependencies. It does not need your project to build; it parses source with `go/parser` only. + +## 1. Run an Audit + +```bash +flaglint-go audit ./services +``` + +Example output from a small service wiring the client through a package-level singleton getter (a pattern flaglint-go resolves across files — see [Identity Model](/docs/go/concepts/identity-model/)): + +```go +// pkg/flags/client.go +var client *ld.LDClient + +func Client() *ld.LDClient { return client } +``` + +```go +// checkout.go +client := flags.Client() +enabled, _ := client.BoolVariation("checkout-v2", ctx, false) +pct, _ := client.IntVariation("discount-percentage", ctx, 0) +``` + +```text +Scan complete — 2 unique flag(s) across 2 call site(s) (0ms, 2 file(s)) +Migration readiness: 100/100 · ready + 2 low risk · 0 medium risk · 0 high risk +``` + +**The three commands and when to use each:** + +- `audit` — risk-ranked overview with a migration-readiness score. Start here. +- `scan` — structured, file-level inventory (JSON/Markdown) for automation or deeper review. +- `validate` — CI gate that blocks new direct LaunchDarkly evaluation calls. + +## 2. Inspect Detailed Inventory With Scan + +```bash +flaglint-go scan ./services --format json +``` + +```json +{ + "scannedFiles": 2, + "totalUsages": 2, + "uniqueFlags": ["checkout-v2", "discount-percentage"], + "usages": [ + { + "flagKey": "checkout-v2", + "isDynamic": false, + "file": "checkout.go", + "line": 13, + "callType": "BoolVariation", + "fingerprint": "launchdarkly:BoolVariation:checkout-v2:checkout.go", + "risk": "low" + } + ] +} +``` + +## 3. Enforce a Policy in CI + +```bash +flaglint-go validate ./services --no-direct-launchdarkly +``` + +```text +✗ validate --no-direct-launchdarkly: 2 direct LaunchDarkly Go SDK call(s) found. + + checkout.go:13:16 — BoolVariation("checkout-v2") + checkout.go:20:12 — IntVariation("discount-percentage") + +These call sites must migrate to OpenFeature before this rule passes. +``` + +`validate` is the only command that ever exits `1` — `scan` and `audit` always exit `0` unless something breaks (a bad directory, malformed config). + +## 4. Adopt Gradually With a Baseline + +Turning on `--no-direct-launchdarkly` immediately fails CI for every existing call site. To adopt flaglint-go without a big-bang migration, capture current debt as a baseline and only fail on *new* debt: + +```bash +# Capture current findings once, commit the file +flaglint-go audit ./services --write-baseline .flaglint-baseline.json + +# In CI: pass as long as no new debt was introduced +flaglint-go validate ./services --baseline .flaglint-baseline.json --fail-on-new +``` + +```text +Scanned 2 file(s). Found 2 LaunchDarkly Go SDK usage(s). +✓ No new findings beyond baseline +``` + +`--baseline`/`--fail-on-new` runs independently of `--no-direct-launchdarkly` — most teams adopt with baseline mode alone, then turn on the strict policy later once existing debt is cleared. + +--- + +**Further reading:** [CLI reference](/docs/go/cli/audit/) · [Identity Model](/docs/go/concepts/identity-model/) · [Enforce in CI guide](/docs/go/guides/enforce-in-ci/) + +## Feedback + +- [Edit this page on GitHub](https://github.com/flaglint/flaglint.dev/edit/main/src/content/docs/docs/go/quickstart.md) +- [Report an issue](https://github.com/flaglint/flaglint-go/issues/new) +- Next: [CLI Reference](/docs/go/cli/audit/) diff --git a/src/content/docs/docs/go/reference/limitations.md b/src/content/docs/docs/go/reference/limitations.md new file mode 100644 index 0000000..499573e --- /dev/null +++ b/src/content/docs/docs/go/reference/limitations.md @@ -0,0 +1,40 @@ +--- +title: flaglint-go Limitations +description: Current boundaries and non-goals of flaglint-go. +lastUpdated: 2026-07-06 +--- + +flaglint-go is intentionally narrow and honest about what it can't yet prove. + +## No Automated Migration + +There is no `migrate`/`--apply` command. flaglint-go audits and enforces policy; it does not rewrite source. If you need automated OpenFeature rewrites today, that capability currently exists only in [flaglint-js](/docs/quickstart/) for Node.js/TypeScript. + +## 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: + +- **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) +- **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) + +Every one of these fails safe: a missed detection, never a false positive. 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 + +flaglint-go does not query LaunchDarkly and does not identify production-stale flags. It cannot know flag age, owner, evaluation history, environment configuration, or production usage from source alone. + +## Outside Detection Coverage + +- Browser/mobile LaunchDarkly SDKs. +- Non-LaunchDarkly feature-flag providers. +- Runtime-only flag key construction that can't be resolved statically (reported as high-risk "dynamic", not silently ignored). + +## Feedback + +- [Edit this page on GitHub](https://github.com/flaglint/flaglint.dev/edit/main/src/content/docs/docs/go/reference/limitations.md) +- [Report an issue](https://github.com/flaglint/flaglint-go/issues/new) +- Next: [flaglint-go Overview](/docs/go/) diff --git a/src/content/docs/docs/go/reference/supported-scope.md b/src/content/docs/docs/go/reference/supported-scope.md new file mode 100644 index 0000000..e11661f --- /dev/null +++ b/src/content/docs/docs/go/reference/supported-scope.md @@ -0,0 +1,52 @@ +--- +title: flaglint-go Supported Scope +description: What flaglint-go detects, reports, and excludes. +lastUpdated: 2026-07-06 +--- + +This page covers **flaglint-go**, the Go CLI. For the JavaScript/TypeScript CLI's scope, see [flaglint-js: Supported Scope](/docs/reference/supported-scope/). + +flaglint-go detects LaunchDarkly Go server-side SDK evaluation calls from: + +- `github.com/launchdarkly/go-server-sdk/v6` +- `github.com/launchdarkly/go-server-sdk/v7` + +## API Coverage + +| Method | Detected | Risk | +| --- | --- | --- | +| `BoolVariation` / `StringVariation` / `IntVariation` / `Float64Variation` | Yes | Low (static key) | +| `*Ctx` forms of the above | Yes | Low (static key) | +| `JSONVariation` (and `*Ctx`) | Yes | Medium | +| `*VariationDetail(Ctx)` methods | Yes | High | +| `AllFlagsState` | Yes | High | +| Dynamic flag keys | Yes | High (overrides the method's own risk) | +| Browser/mobile LaunchDarkly SDKs | No | — | +| Non-LaunchDarkly providers | No | — | + +## 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: + +| 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 | +| 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 — [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 documented false-negative risk, never a false-positive one — flaglint-go's non-negotiable rule is to under-detect rather than guess. See [Limitations](/docs/go/reference/limitations/). + +## Feedback + +- [Edit this page on GitHub](https://github.com/flaglint/flaglint.dev/edit/main/src/content/docs/docs/go/reference/supported-scope.md) +- [Report an issue](https://github.com/flaglint/flaglint-go/issues/new) +- Next: [Limitations](/docs/go/reference/limitations/) From 330b7280be4cfea0fd5def8473ea5a022c2a3c31 Mon Sep 17 00:00:00 2001 From: Krishan Kant Sharma Date: Mon, 6 Jul 2026 14:33:59 -0500 Subject: [PATCH 3/3] docs(go): fix two accuracy issues caught in review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The blanket "every gap is a false-negative, never a false-positive" claim was wrong for one row: block-scoped variable shadowing (issue #5) is a documented false-positive risk, not a false-negative one — verified by reproducing it against the built binary. Carved out the exception in both supported-scope.md and limitations.md instead of overclaiming. - The command-comparison table said flaglint-go has no `init`/`completion`; it actually does ship a working `completion` command (Cobra's stock boilerplate, not bespoke) — split the row and corrected it. `init` really doesn't exist yet. Signed-off-by: Krishan Kant Sharma --- src/content/docs/docs/go/index.md | 3 ++- src/content/docs/docs/go/reference/limitations.md | 4 ++-- src/content/docs/docs/go/reference/supported-scope.md | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/content/docs/docs/go/index.md b/src/content/docs/docs/go/index.md index 11383be..d83acc2 100644 --- a/src/content/docs/docs/go/index.md +++ b/src/content/docs/docs/go/index.md @@ -31,7 +31,8 @@ flaglint-go is an early-access, separate binary — not a Go mode bolted onto th | `audit` — risk-ranked report + readiness score | ✅ | ✅ | | `validate` — CI policy gate | ✅ | ✅ | | `migrate` — automated OpenFeature rewrites | ✅ | ❌ not yet | -| `init` / `completion` | ✅ | ❌ not yet | +| `init` — scaffold a config file | ✅ | ❌ not yet | +| `completion` — shell autocompletion scripts | ✅ | ✅ (Cobra's stock boilerplate — bash/zsh/fish/PowerShell) | ## What flaglint-go Does diff --git a/src/content/docs/docs/go/reference/limitations.md b/src/content/docs/docs/go/reference/limitations.md index 499573e..a77af89 100644 --- a/src/content/docs/docs/go/reference/limitations.md +++ b/src/content/docs/docs/go/reference/limitations.md @@ -17,11 +17,11 @@ flaglint-go proves client identity from syntax alone — no build, no `go/types` - **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) +- **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) -Every one of these fails safe: a missed detection, never a false positive. 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. +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. ## 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 e11661f..54d9473 100644 --- a/src/content/docs/docs/go/reference/supported-scope.md +++ b/src/content/docs/docs/go/reference/supported-scope.md @@ -39,11 +39,11 @@ flaglint-go proves client identity syntactically (no `go/types`, no build requir | 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 — [tracked](https://github.com/flaglint/flaglint-go/issues/5) | +| 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 documented false-negative risk, never a false-positive one — flaglint-go's non-negotiable rule is to under-detect rather than guess. See [Limitations](/docs/go/reference/limitations/). +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/). ## Feedback