Skip to content

feat(scanner): implement migrationInventory for cross-tool parity#38

Open
flaglint wants to merge 1 commit into
mainfrom
feat/migration-inventory
Open

feat(scanner): implement migrationInventory for cross-tool parity#38
flaglint wants to merge 1 commit into
mainfrom
feat/migration-inventory

Conversation

@flaglint

@flaglint flaglint commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #37 — implements flaglint-js's migrationInventory field for real cross-tool parity, ahead of the spec's own "optional until v1.2" deadline for other implementations.

  • types.MigrationInventoryItem mirrors flaglint-js's shape field-for-field (see docs/adr/003-cross-tool-contract.md for the full mapping) — one deliberate omission: flaglint-js's isAwaited has no Go equivalent (no async/await).
  • valueType is always derived directly from the Go SDK method name (every method is already type-specific, unlike flaglint-js's generic variation()), so "unknown-fallback" is never actually produced by flaglint-go itself.
  • manualReviewReason/safelyAutomatable otherwise mirror flaglint-js's logic exactly: bulk-inventory-call, dynamic-key, detail-method, else safely automatable.
  • Wired into the same detect() call site that already builds each FlagUsageparsedFile now retains each file's raw source bytes (previously discarded after parsing) so buildMigrationInventoryItem can extract exact expression text via byte offsets.
  • Interface-satisfaction/transitive-factory-wrapping --strict-types usages get full migration items (their call sites are ordinary client.Method(key, ctx, fallback) shapes). Forwarding-function usages deliberately get none — their call site doesn't directly show the LD method's own arguments, so an item would misrepresent what's actually rewritable.

Also fixes a latent pre-existing bug found while verifying this: the "empty arrays must not be null" tests checked for "field":null (no space) but json.MarshalIndent emits "field": null (with a space) — vacuously true regardless of actual values. Fixed to match real formatting.

Verification

  • Full test suite green, including new coverage: a regression test covering dynamic-key/detail-method/bulk-call/*Ctx-variant argument shifting, plus assertions on the existing interface-satisfaction and forwarding-call --strict-types tests proving the inclusion/exclusion split.
  • Manually confirmed exact field values via the built binary against a synthetic fixture covering all five method shapes.
  • No crashes/regressions scanning weaviate and mint-app — migration item count matches usage count in both.

Closes #37.

flaglint-js's ScanResult carries an optional migrationInventory field —
a richer, migration-focused record of each call site meant to drive a
future migrate rewriter. The cross-tool spec marks this optional for
other implementations "until spec v1.2", but since flaglint-js already
emits it, this implements it now for real parity (ADR 003) rather than
waiting — flaglint-go still has no migrate/--apply command itself (ADR
002's Phase 1 scope); this is the data foundation a future command would
need, not the command.

Mirrors flaglint-js's MigrationInventoryItem field-for-field (types.go):
file/line/column, launchDarklyMethod, callExpression + rangeStart/
rangeEnd (raw source text and byte-offset range of the whole call),
flagKeyExpression/staticFlagKey, isDynamic, valueType, fallbackExpression,
evaluationContextExpression, safelyAutomatable, manualReviewReason. One
deliberate omission: flaglint-js's isAwaited has no Go equivalent (no
async/await) and is never emitted.

Unlike flaglint-js — whose generic variation()/isFeatureEnabled() calls
need valueType inferred from the fallback argument's runtime type — every
Go SDK method name is already type-specific (BoolVariation/
StringVariation/IntVariation/Float64Variation/JSONVariation), so
valueType is always derived directly from the method name
(migration.go's migrationValueTypes map); "unknown-fallback" is kept in
the type for cross-tool consistency but flaglint-go itself never
produces it. manualReviewReason otherwise mirrors flaglint-js's logic
exactly: bulk-inventory-call for AllFlagsState, dynamic-key when the flag
key isn't a literal, detail-method for *VariationDetail(Ctx) methods,
else safelyAutomatable: true.

Wired into fileDetector.detect (scanner.go) at the same call site that
already builds each FlagUsage, reusing the same call/spec/flagKey/
isDynamic already computed there — buildMigrationInventoryItem
(migration.go) extracts each argument's exact source text via byte
offsets against the file's raw source bytes, now retained in parsedFile
(previously discarded once go/parser succeeded).

Both --strict-types-derived usage sources get different treatment,
matching what's actually safe to claim:
- interface satisfaction / transitive factory wrapping (strictTypesUsages,
  a re-run of the same runWholeScanAnalysis/detect() path) get full
  migration items for free, since their call sites are ordinary
  client.Method(key, context, fallback) shapes — only how the receiver's
  identity gets proven differs. strict.go now separately re-reads each
  file's source bytes for this pass (go/packages' own loaded ASTs don't
  retain the raw text any more than Scan()'s own parser.ParseFile does).
- forwarding-function usages (detectForwardingCallUsages) get no
  migration item at all: their call site doesn't directly show the LD
  method's own (key, context, fallback) arguments (it shows the wrapper
  call, e.g. callDirect(client.BoolVariation, key, def)), so an item for
  one would misrepresent what's actually safely rewritable.
  mergeStrictTypesUsages now merges migration items index-aligned with
  the usages they came from, when present.

Also fixes a latent, pre-existing bug found while verifying this: the
existing "empty arrays must not be null" tests (reporter and CLI)
checked for `"field":null` (no space) but json.MarshalIndent always
emits `"field": null` (with a space) — the checks were vacuously true
regardless of the actual field values. Fixed both to match real
formatting and construct explicit non-nil-but-empty inputs where the
old code relied on Go's zero-value nil slice, which was never a valid
stand-in for what Scan() itself actually guarantees.

Verified: full test suite green, including new regression coverage
(TestScanFile_positiveVarietyMigrationInventory covering dynamic-key,
detail-method, bulk-call, and *Ctx-variant argument-shifting cases; new
assertions in the existing interface-satisfaction and forwarding-call
--strict-types tests proving the inclusion/exclusion split above).
Manually confirmed exact field values via the built binary against a
synthetic fixture covering all five method shapes, and confirmed no
crashes/regressions scanning weaviate and mint-app (item count matches
usage count in both, values sane).

Signed-off-by: Krishan Kant Sharma <krishansharma0327@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@flaglint, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 88e22e98-1124-46ef-991f-c5079e52f07b

📥 Commits

Reviewing files that changed from the base of the PR and between 2865f5f and d4619a3.

📒 Files selected for processing (9)
  • docs/adr/003-cross-tool-contract.md
  • internal/cli/cli_test.go
  • internal/reporter/reporter_test.go
  • internal/scanner/migration.go
  • internal/scanner/scanner.go
  • internal/scanner/scanner_test.go
  • internal/scanner/strict.go
  • internal/scanner/strict_test.go
  • internal/types/types.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/migration-inventory

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

scan-result: implement migrationInventory (currently optional per spec, but flaglint-js already emits it)

2 participants