Skip to content

feat(core): add stable finding fingerprints to scanner output#175

Merged
Krishan27 merged 2 commits into
mainfrom
feat/stable-fingerprints
Jun 23, 2026
Merged

feat(core): add stable finding fingerprints to scanner output#175
Krishan27 merged 2 commits into
mainfrom
feat/stable-fingerprints

Conversation

@Krishan27

@Krishan27 Krishan27 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Implements ADR 007. Adds fingerprint field to FlagUsage (launchdarkly:callType:flagKey:normalizedFilePath). Stable across line-number changes. Foundation for baseline mode. Part of the v1.0.0 milestone.

Summary by CodeRabbit

  • New Features

    • Added fingerprinting for flag usage detection, enabling stable identification and tracking of unique usage patterns across the codebase.
  • Tests

    • Updated test fixtures and helpers across migrator, scanner, readiness, reporter, and validator tests to include new fingerprint data.

Implements ADR 007. Adds fingerprint: string to FlagUsage using schema:
  launchdarkly:callType:flagKey:normalizedFilePath

Stable across line-number changes. Dynamic keys get a sequential index.
Part of the v1.0.0 milestone.

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

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a fingerprint: string field to the FlagUsage type and a new generateFingerprint function in src/scanner/fingerprint.ts that produces a stable launchdarkly:<callType>:<flagKey>:<normalizedPath>[:<dynamicIndex>] string. The scanner and migrator are updated to populate this field across all detection paths, and all test fixtures are updated accordingly.

Changes

Stable FlagUsage Fingerprint

Layer / File(s) Summary
FlagUsage type contract and generateFingerprint implementation
src/types.ts, src/scanner/fingerprint.ts, src/scanner/tests/fingerprint.test.ts
FlagUsage gains a required fingerprint: string field. The new fingerprint.ts module exports generateFingerprint, which normalizes paths (Windows separators, leading ./) and builds a launchdarkly:<callType>:<flagKey>:<normalizedPath>[:<dynamicIndex>] string; tests verify composition, normalization, wildcard indexing, and determinism.
Scanner: fingerprint population across all detection paths
src/scanner/index.ts
Imports generateFingerprint, initializes a per-file dynamicIndex counter, and sets fingerprint on every pushed usages entry: allFlags/allFlagsState, variation methods, isFeatureEnabled, React hooks (useFlags/useLDClient), wrapper calls, withLDConsumer HOC, and JSX <LDProvider> elements.
Migrator: fingerprint in evidence item builder
src/migrator/index.ts
Imports generateFingerprint; buildEvidenceItem now derives a usageFlagKey local and adds fingerprint to the constructed usage object.
Test fixture updates across all suites
src/migrator/tests/migrator.test.ts, src/readiness/readiness.test.ts, src/reporter/tests/reporter.test.ts, src/validator/tests/validate.test.ts
All FlagUsage fixture builders and inline objects in the migrator, readiness, reporter, and validator test suites are updated to include the required fingerprint field matching the format produced by generateFingerprint.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

  • flaglint/flaglint#160: Defines the same stable finding fingerprint scheme (provider:callType:flagKey:normalizedFilePath with per-file dynamic indexing) that this PR implements in code and tests.

Poem

🐇 Hop, hop, hooray for the fingerprint day!
Each flag gets a tag that will never astray,
launchdarkly:variation:my-flag:src/app
No matter the line, it stays crisp and exact.
The rabbit has labeled each feature with care,
Stable and hashed through the cold winter air! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(core): add stable finding fingerprints to scanner output' accurately and specifically summarizes the main change: adding stable fingerprint functionality to scanner output for identifying flag findings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/stable-fingerprints
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/stable-fingerprints

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 23, 2026

Copy link
Copy Markdown

Deploying flaglint with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5ca1009
Status:⚡️  Build in progress...

View logs

@Krishan27 Krishan27 merged commit 7be12d7 into main Jun 23, 2026
10 of 12 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/readiness/readiness.test.ts (1)

23-34: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

makeUsage fingerprint should be derived after applying overrides.

Line 31 hardcodes a fingerprint, while overrides are merged in the same object. Overriding flagKey, callType, or file without also overriding fingerprint creates invalid fixture state.

💡 Suggested fix
 function makeUsage(overrides: Partial<FlagUsage> = {}): FlagUsage {
-  return {
+  const usage = {
     flagKey: "my-flag",
     isDynamic: false,
     file: "src/app.ts",
     line: 1,
     column: 0,
     callType: "boolVariation",
-    fingerprint: "launchdarkly:boolVariation:my-flag:src/app.ts",
     stalenessSignals: [],
     ...overrides,
   };
+  return {
+    ...usage,
+    fingerprint:
+      overrides.fingerprint ??
+      `launchdarkly:${usage.callType}:${usage.flagKey}:${usage.file}`,
+  };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/readiness/readiness.test.ts` around lines 23 - 34, The makeUsage function
hardcodes the fingerprint value before applying overrides, which means when
flagKey, callType, or file are overridden, the fingerprint becomes inconsistent
with the actual values. Refactor makeUsage to first merge the default object
with overrides, then compute the fingerprint dynamically based on the final
values of flagKey, callType, and file. This ensures the fingerprint is always
valid and reflects the actual properties of the FlagUsage object being created.
src/migrator/tests/migrator.test.ts (1)

22-33: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

makeUsage can emit mismatched fixture data when overrides change key/type/path.

On Line 30, fingerprint is fixed to legacy defaults, but Line 32 applies ...overrides afterward. If a test overrides flagKey, callType, or file without overriding fingerprint, the helper returns an inconsistent FlagUsage.

💡 Suggested fix
 function makeUsage(overrides: Partial<FlagUsage>): FlagUsage {
-  return {
+  const usage = {
     flagKey: "legacy-flag",
     isDynamic: false,
     file: "src/legacy.ts",
     line: 3,
     column: 4,
     callType: "variation",
-    fingerprint: "launchdarkly:variation:legacy-flag:src/legacy.ts",
     stalenessSignals: [],
     ...overrides,
   };
+  return {
+    ...usage,
+    fingerprint:
+      overrides.fingerprint ??
+      `launchdarkly:${usage.callType}:${usage.flagKey}:${usage.file}`,
+  };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/migrator/tests/migrator.test.ts` around lines 22 - 33, The makeUsage
helper function has a fixture data consistency issue where the hardcoded
fingerprint on line 30 does not get updated when overrides modify flagKey,
callType, or file. Move the fingerprint computation to occur after the overrides
are applied so it is dynamically constructed based on the actual values of
flagKey, callType, and file in the returned object. This ensures that whenever
these properties are overridden in tests, the fingerprint remains consistent
with the actual flag usage data.
🧹 Nitpick comments (2)
src/validator/tests/validate.test.ts (1)

15-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer shared fingerprint generation in makeScanResult defaults.

Line 26 reconstructs the fingerprint string manually. Reusing generateFingerprint keeps validator fixtures aligned with scanner/migrator rules (notably normalization/coercion behavior) as the schema evolves.

💡 Suggested refactor
+import { generateFingerprint } from "../../scanner/fingerprint";

       callType,
-      fingerprint: u.fingerprint ?? `launchdarkly:${callType}:${flagKey}:${file}`,
+      fingerprint: u.fingerprint ?? generateFingerprint(flagKey, callType, file),
       stalenessSignals: u.stalenessSignals ?? [],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/validator/tests/validate.test.ts` around lines 15 - 27, The test fixture
in the map function that constructs the default FlagUsage objects is manually
building the fingerprint string instead of using the shared generateFingerprint
utility function. Replace the manual fingerprint string construction (the line
with the template literal launchdarkly:${callType}:${flagKey}:${file}) with a
call to generateFingerprint, passing the appropriate parameters (flagKey,
callType, and file) to ensure test fixtures use the same fingerprint generation
logic and normalization behavior as the actual scanner/migrator implementation.
src/migrator/tests/migrator.test.ts (1)

49-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use generateFingerprint in makeResult to mirror production behavior exactly.

Line 56 re-implements the format inline. That bypasses shared rules (path normalization and key coercion) already defined in src/scanner/fingerprint.ts, so test fixtures can drift from runtime behavior.

💡 Suggested refactor
+import { generateFingerprint } from "../../scanner/fingerprint";

     usages: migrationInventory.map((item) => ({
       flagKey: item.staticFlagKey ?? (item.isDynamic ? "dynamic" : "*"),
       isDynamic: item.isDynamic,
       file: item.file,
       line: item.line,
       column: item.column,
       callType: item.launchDarklyMethod,
-      fingerprint: `launchdarkly:${item.launchDarklyMethod}:${item.staticFlagKey ?? (item.isDynamic ? "dynamic" : "*")}:${item.file}`,
+      fingerprint: generateFingerprint(
+        item.staticFlagKey ?? (item.isDynamic ? "dynamic" : "*"),
+        item.launchDarklyMethod,
+        item.file
+      ),
       stalenessSignals: [],
     })),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/migrator/tests/migrator.test.ts` around lines 49 - 57, The fingerprint
field in the usages mapping within makeResult is constructing the fingerprint
value inline using a template string instead of using the shared
generateFingerprint function from src/scanner/fingerprint.ts. Replace the inline
fingerprint template string construction with a call to the generateFingerprint
function, passing the appropriate parameters (item.launchDarklyMethod,
item.staticFlagKey, item.isDynamic, and item.file) to ensure the test behavior
respects the same path normalization and key coercion rules that are applied at
runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/migrator/index.ts`:
- Around line 101-110: The generateFingerprint call in buildEvidenceItem does
not include the dynamicIndex parameter for dynamic items, causing multiple
dynamic usages in the same file and method to produce identical fingerprints.
Fix this by passing item.dynamicIndex (or the appropriate dynamic index field)
to the generateFingerprint function when item.isDynamic is true, ensuring each
distinct dynamic usage receives a unique fingerprint that matches the
scanner-side contract.

---

Outside diff comments:
In `@src/migrator/tests/migrator.test.ts`:
- Around line 22-33: The makeUsage helper function has a fixture data
consistency issue where the hardcoded fingerprint on line 30 does not get
updated when overrides modify flagKey, callType, or file. Move the fingerprint
computation to occur after the overrides are applied so it is dynamically
constructed based on the actual values of flagKey, callType, and file in the
returned object. This ensures that whenever these properties are overridden in
tests, the fingerprint remains consistent with the actual flag usage data.

In `@src/readiness/readiness.test.ts`:
- Around line 23-34: The makeUsage function hardcodes the fingerprint value
before applying overrides, which means when flagKey, callType, or file are
overridden, the fingerprint becomes inconsistent with the actual values.
Refactor makeUsage to first merge the default object with overrides, then
compute the fingerprint dynamically based on the final values of flagKey,
callType, and file. This ensures the fingerprint is always valid and reflects
the actual properties of the FlagUsage object being created.

---

Nitpick comments:
In `@src/migrator/tests/migrator.test.ts`:
- Around line 49-57: The fingerprint field in the usages mapping within
makeResult is constructing the fingerprint value inline using a template string
instead of using the shared generateFingerprint function from
src/scanner/fingerprint.ts. Replace the inline fingerprint template string
construction with a call to the generateFingerprint function, passing the
appropriate parameters (item.launchDarklyMethod, item.staticFlagKey,
item.isDynamic, and item.file) to ensure the test behavior respects the same
path normalization and key coercion rules that are applied at runtime.

In `@src/validator/tests/validate.test.ts`:
- Around line 15-27: The test fixture in the map function that constructs the
default FlagUsage objects is manually building the fingerprint string instead of
using the shared generateFingerprint utility function. Replace the manual
fingerprint string construction (the line with the template literal
launchdarkly:${callType}:${flagKey}:${file}) with a call to generateFingerprint,
passing the appropriate parameters (flagKey, callType, and file) to ensure test
fixtures use the same fingerprint generation logic and normalization behavior as
the actual scanner/migrator implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b068fcd3-9db7-4cd1-a64d-83254cfb2975

📥 Commits

Reviewing files that changed from the base of the PR and between 573c5bd and 744680c.

📒 Files selected for processing (9)
  • src/migrator/index.ts
  • src/migrator/tests/migrator.test.ts
  • src/readiness/readiness.test.ts
  • src/reporter/tests/reporter.test.ts
  • src/scanner/fingerprint.ts
  • src/scanner/index.ts
  • src/scanner/tests/fingerprint.test.ts
  • src/types.ts
  • src/validator/tests/validate.test.ts

Comment thread src/migrator/index.ts Outdated
Comment on lines +101 to +110
const usageFlagKey = item.staticFlagKey ?? (item.isDynamic ? "dynamic" : "*");
return {
usage: {
flagKey,
flagKey: usageFlagKey,
isDynamic: item.isDynamic,
file: item.file,
line: item.line,
column: item.column,
callType: item.launchDarklyMethod,
fingerprint: generateFingerprint(flagKey, item.launchDarklyMethod, item.file),
fingerprint: generateFingerprint(usageFlagKey, item.launchDarklyMethod, item.file),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Dynamic-key fingerprints in migrator can collide.

buildEvidenceItem never passes dynamicIndex to generateFingerprint, so multiple dynamic items in the same file/method can produce identical fingerprints. Scanner-side dynamic usages already add a per-file index, so this creates a contract mismatch and can collapse distinct evidence findings.

🔧 Proposed fix
-function buildEvidenceItem(item: MigrationInventoryItem): MigrationItem {
+function buildEvidenceItem(item: MigrationInventoryItem, dynamicIndex?: number): MigrationItem {
   const flagLabel = item.staticFlagKey ?? item.flagKeyExpression ?? "*";
@@
   const usageFlagKey = item.staticFlagKey ?? (item.isDynamic ? "dynamic" : "*");
   return {
     usage: {
       flagKey: usageFlagKey,
       isDynamic: item.isDynamic,
       file: item.file,
       line: item.line,
       column: item.column,
       callType: item.launchDarklyMethod,
-      fingerprint: generateFingerprint(usageFlagKey, item.launchDarklyMethod, item.file),
+      fingerprint: generateFingerprint(
+        usageFlagKey,
+        item.launchDarklyMethod,
+        item.file,
+        dynamicIndex
+      ),
       stalenessSignals: [],
     },
@@
 export function analyze(result: ScanResult): MigrationAnalysis {
   const inventoryItems = inventoryFrom(result);
-  const items = inventoryItems.map(buildEvidenceItem);
+  const dynamicIndexByFile = new Map<string, number>();
+  const items = inventoryItems.map((item) => {
+    if (!item.isDynamic) return buildEvidenceItem(item);
+    const idx = dynamicIndexByFile.get(item.file) ?? 0;
+    dynamicIndexByFile.set(item.file, idx + 1);
+    return buildEvidenceItem(item, idx);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/migrator/index.ts` around lines 101 - 110, The generateFingerprint call
in buildEvidenceItem does not include the dynamicIndex parameter for dynamic
items, causing multiple dynamic usages in the same file and method to produce
identical fingerprints. Fix this by passing item.dynamicIndex (or the
appropriate dynamic index field) to the generateFingerprint function when
item.isDynamic is true, ensuring each distinct dynamic usage receives a unique
fingerprint that matches the scanner-side contract.

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.

1 participant