π 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
-
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
-
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
-
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
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
π Duplicate Code Detected: IndentedStringBuilder Duplicated Across Source Generators
Analysis of commit 3e056b0
Assignee:
@copilotSummary
The
IndentedStringBuilderhelper class β aStringBuilderwrapper that tracks an indentation level for source-code generation β is duplicated in two separate Roslyn source-generator projects:MSTest.SourceGenerationandMSTest.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
src/Analyzers/MSTest.SourceGeneration/Helpers/IndentedStringBuilder.cs(83 lines)src/Analyzers/MSTest.AotReflection.SourceGeneration/Helpers/IndentedStringBuilder.cs(92 lines)Both files share:
StringBuilderwrapper tracking indentationIDisposablescope class to close{...}blocks automatically_needsIndentboolean to lazily insert indentation on the first write per line4-spaceindent unitDifferences:
...SourceGeneration.HelpersvsMSTest.AotReflection.SourceGeneration.Helpers)this; the SourceGeneration version returnsvoid_builder.AppendLine()(platform newline); the SourceGeneration version usesConstants.NewLineDisposableActionvsBlockScopeImpact Analysis
Refactoring Recommendations
Unify into a shared Analyzers helper project:
IndentedStringBuilderwith bothvoidand fluent overloads tosrc/Analyzers/MSTest.Analyzers/Helpers/(or a new shared analyzer helpers project)MSTest.SourceGenerationandMSTest.AotReflection.SourceGenerationSource-link via linked file:
<Compile Include="../MSTest.SourceGeneration/Helpers/IndentedStringBuilder.cs" Link="Helpers/IndentedStringBuilder.cs" />Align APIs before consolidating:
Constants.NewLinevsEnvironment.NewLineImplementation Checklist
Analysis Metadata
Add this agentic workflows to your repo
To install this agentic workflow, run