Skip to content

[duplicate-code] Duplicate Code: IndentedStringBuilder Duplicated Across MSTest Source Generator ProjectsΒ #8987

@Evangelink

Description

@Evangelink

πŸ” Duplicate Code Detected: IndentedStringBuilder Duplicated Across Source Generators

Analysis of commit 3e056b0

Assignee: @copilot

Summary

The IndentedStringBuilder helper class β€” a StringBuilder wrapper that tracks an indentation level for source-code generation β€” is duplicated in two separate Roslyn source-generator projects: MSTest.SourceGeneration and MSTest.AotReflection.SourceGeneration. Both files implement the same core concept (append text with automatic indentation, open/close curly-brace blocks) with slightly different APIs but identical intent.

Duplication Details

Pattern: IndentedStringBuilder helper class

  • Severity: Low-Medium
  • Occurrences: 2 instances
  • Locations:
    • src/Analyzers/MSTest.SourceGeneration/Helpers/IndentedStringBuilder.cs (83 lines)
    • src/Analyzers/MSTest.AotReflection.SourceGeneration/Helpers/IndentedStringBuilder.cs (92 lines)
  • Code Sample (shared indent-tracking pattern):
// MSTest.SourceGeneration version uses IndentationLevel property:
public int IndentationLevel { get; internal set; }
public void Append(string value) { MaybeAppendIndent(); ... }
public void AppendLine(string value) { MaybeAppendIndent(); ... }
public IDisposable AppendBlock(string header) { AppendLine(header); AppendLine("{"); IndentationLevel++; ... }

// MSTest.AotReflection.SourceGeneration version uses _indent field:
private int _indent;
public IndentedStringBuilder Append(string value) { EnsureIndent(); ... return this; }
public IndentedStringBuilder AppendLine(string value) { EnsureIndent(); ... return this; }
public IDisposable Block(string? header = null) { ... _indent++; return new BlockScope(this); }

Both files share:

  • Same concept of a StringBuilder wrapper tracking indentation
  • Same nested IDisposable scope class to close {...} blocks automatically
  • Same _needsIndent boolean to lazily insert indentation on the first write per line
  • Same 4-space indent unit

Differences:

  • Namespace (...SourceGeneration.Helpers vs MSTest.AotReflection.SourceGeneration.Helpers)
  • The AotReflection version has a fluent (builder-style) API returning this; the SourceGeneration version returns void
  • The AotReflection version uses _builder.AppendLine() (platform newline); the SourceGeneration version uses Constants.NewLine
  • The inner scope class is named DisposableAction vs BlockScope
  • The AotReflection version's comment explicitly notes it is a private PoC copy to avoid pulling in the SourceGeneration helper

Impact Analysis

  • Maintainability: Any improvement to indentation handling (e.g., different indent widths, CR/LF normalization, performance optimization) must be applied to both files separately.
  • Bug Risk: The APIs differ slightly, which can cause inconsistencies in generated code formatting between the two generators.
  • Code Bloat: ~80 lines duplicated across the two source-generator projects.

Refactoring Recommendations

  1. Unify into a shared Analyzers helper project:

    • Move a single IndentedStringBuilder with both void and fluent overloads to src/Analyzers/MSTest.Analyzers/Helpers/ (or a new shared analyzer helpers project)
    • Reference it from both MSTest.SourceGeneration and MSTest.AotReflection.SourceGeneration
  2. Source-link via linked file:

    • Add the SourceGeneration version as a linked file in the AotReflection project:
      <Compile Include="../MSTest.SourceGeneration/Helpers/IndentedStringBuilder.cs" Link="Helpers/IndentedStringBuilder.cs" />
    • Standardize the API between the two projects first
  3. Align APIs before consolidating:

    • Decide on fluent vs void return style and on Constants.NewLine vs Environment.NewLine
    • Estimated effort: 1–2 hours

Implementation Checklist

  • Review duplication findings
  • Align API (fluent vs void, newline handling)
  • Extract to a shared location or use linked file
  • Update both source generators to use the shared helper
  • Verify generated source output is unchanged

Analysis Metadata

  • Analyzed Files: 2
  • Duplicated Lines: ~75 lines of shared logic
  • Detection Method: Semantic code analysis + diff comparison
  • Commit: 3e056b0
  • Analysis Date: 2026-06-10

πŸ€– Automated content by GitHub Copilot. Posted via a maintainer's GitHub token, so it appears under their account β€” the account owner did not write or approve this content personally. Generated by the Duplicate Code Detector workflow.{ai_credits_suffix} Β· [β—·]( Β· β—·)

Add this agentic workflows to your repo

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/duplicate-code-detector.md@main
  • expires on Jun 12, 2026, 5:55 AM UTC

Metadata

Metadata

Labels

type/automationCreated or maintained by an agentic workflow.type/tech-debtCode health, refactoring, simplification.

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions