microsoft / microsoft/FluidFramework

Duplicate code: MutableStringInterner duplicated in framework and experimental tree

Open
#26,877 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
4.9k
Forks
586
Avg merge
1d 15h
Merged PRs (30d)
146

Description

🔍 Duplicate Code Detected: MutableStringInterner

Analysis of commit edf996f5e3b26fc2478218abdaee30e68b4af010

Assignee: @copilot

Summary

MutableStringInterner (and its supporting StringInterner interface shape) is implemented twice in different packages with near-identical logic, creating ongoing maintenance risk and inconsistent behavior (notably differing error semantics in getString).

Duplication Details

Pattern: Near-identical MutableStringInterner implementation
  • Severity: Medium

  • Occurrences: 2

  • Locations:

    • packages/framework/attributor/src/stringInterner.ts (lines 31-89)
    • experimental/dds/tree/src/StringInterner.ts (lines 23-74)
  • Code Sample (abridged, showing the duplicated core):

    export class MutableStringInterner implements StringInterner {
    	private readonly stringToInternedIdMap = new Map(string, InternedStringId)();
    	private readonly internedStrings: string[] = [];
    
    	constructor(inputStrings: readonly string[] = []) {
    		for (const value of inputStrings) {
    			this.getOrCreateInternedId(value);
    		}
    	}
    
    	public getOrCreateInternedId(input: string): InternedStringId {
    		return this.getInternedId(input) ?? this.createNewId(input);
    	}
    
    	public getInternedId(input: string): InternedStringId | undefined {
    		return this.stringToInternedIdMap.get(input);
    	}
    
    	public getString(internId: number): string {
    		// differs only in the error behavior (UsageError vs fail)
    		/* ... */
    	}
    
    	public getSerializable(): readonly string[] {
    		return this.internedStrings;
    	}
    
    	private createNewId(input: string): InternedStringId {
    		const internedId = this.stringToInternedIdMap.size as InternedStringId;
    		this.stringToInternedIdMap.set(input, internedId);
    		this.internedStrings.push(input);
    		return internedId;
    	}
    }
    

Impact Analysis

  • Maintainability: Fixes/improvements must be applied twice; comments and error-handling have already diverged.
  • Bug Risk: Different failure behavior (UsageError throw vs fail(...)) can cause subtle differences across call sites.
  • Code Bloat: ~40-50 LOC duplicated across two packages, plus duplicated interface definitions.

Refactoring Recommendations

  1. Choose a single canonical implementation

    • Option A: Move the implementation to a small shared internal utility module (e.g. packages/common/* or an existing lightweight shared package) and import it from both locations.
    • Option B: If the experimental tree implementation can depend on the framework version (or vice-versa), re-export/use the existing one and delete the duplicate.
    • Benefits: Single source of truth; consistent semantics.
  2. Align error semantics

    • Decide whether out-of-range IDs should throw UsageError or use fail(...), then standardize.
    • Benefits: Consistent behavior and diagnostics across packages.

Implementation Checklist

  • Confirm intended dependency direction between packages/framework/attributor and experimental/dds/tree
  • Pick a shared location for the canonical implementation
  • Update both call sites to import/re-export
  • Standardize getString failure semantics
  • Run relevant package builds/tests

Analysis Metadata

  • Analyzed Files: 2 (targeted semantic search based on getString/StringInterner hits)
  • Detection Method: Serena semantic code analysis
  • Commit: edf996f5e3b26fc2478218abdaee30e68b4af010
  • Analysis Date: 2026-03-29T21:50:29Z

Generated by Duplicate Code Detector ·

To install this agentic workflow, run

gh aw add github/gh-aw/.github/workflows/duplicate-code-detector.md@94662b1dee8ce96c876ba9f33b3ab8be32de82a4

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by comparing packages/framework/attributor/src/stringInterner.ts with experimental/dds/tree/src/StringInterner.ts and confirm the dependency direction between the packages. Choose a canonical shared implementation, update both locations to use it, and align getString failure semantics. The work is done when the duplicate implementation and interface shape are removed or reused and the relevant package builds and tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
distributed-systems
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.