Analysis of commit 756cdee
Assignee: @copilot
Summary
Six code fixer classes in src/Analyzers/MSTest.Analyzers.CodeFixes/ are structurally identical (49 lines each), differing only in class name, diagnostic ID, and two boolean parameters (isParameterLess, shouldBeStatic). This is ~294 lines total with ~240+ lines of pure duplication.
Duplication Details
Pattern: Identical FixtureMethodFixer-based Code Fixers
- Severity: High
- Occurrences: 6 nearly-identical files
- Locations:
src/Analyzers/MSTest.Analyzers.CodeFixes/AssemblyCleanupShouldBeValidFixer.cs (lines 1–49)
src/Analyzers/MSTest.Analyzers.CodeFixes/AssemblyInitializeShouldBeValidFixer.cs (lines 1–49)
src/Analyzers/MSTest.Analyzers.CodeFixes/ClassCleanupShouldBeValidFixer.cs (lines 1–49)
src/Analyzers/MSTest.Analyzers.CodeFixes/ClassInitializeShouldBeValidFixer.cs (lines 1–49)
src/Analyzers/MSTest.Analyzers.CodeFixes/TestCleanupShouldBeValidFixer.cs (lines 1–49)
src/Analyzers/MSTest.Analyzers.CodeFixes/TestInitializeShouldBeValidFixer.cs (lines 1–49)
The only differences across the 6 files:
| File |
DiagnosticId |
isParameterLess |
shouldBeStatic |
AssemblyCleanupShouldBeValidFixer |
AssemblyCleanupShouldBeValidRuleId |
true |
true |
AssemblyInitializeShouldBeValidFixer |
AssemblyInitializeShouldBeValidRuleId |
false |
true |
ClassCleanupShouldBeValidFixer |
ClassCleanupShouldBeValidRuleId |
true |
true |
ClassInitializeShouldBeValidFixer |
ClassInitializeShouldBeValidRuleId |
false |
true |
TestCleanupShouldBeValidFixer |
TestCleanupShouldBeValidRuleId |
true |
false |
TestInitializeShouldBeValidFixer |
TestInitializeShouldBeValidRuleId |
true |
false |
- Code Sample (representative — all 6 files look like this):
// e.g., TestCleanupShouldBeValidFixer.cs (lines 22–49)
public sealed class TestCleanupShouldBeValidFixer : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DiagnosticIds.TestCleanupShouldBeValidRuleId);
public override FixAllProvider GetFixAllProvider()
=> WellKnownFixAllProviders.BatchFixer;
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SyntaxNode root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
SyntaxNode node = root.FindNode(context.Span);
if (context.Diagnostics.Any(d => !d.Properties.ContainsKey(DiagnosticDescriptorHelper.CannotFixPropertyKey)))
{
context.RegisterCodeFix(
CodeAction.Create(
CodeFixResources.FixSignatureCodeFix,
ct => FixtureMethodFixer.FixSignatureAsync(context.Document, root, node, isParameterLess: true, shouldBeStatic: false, ct),
nameof(TestCleanupShouldBeValidFixer)),
context.Diagnostics);
}
}
}
Impact Analysis
- Maintainability: Any change to
RegisterCodeFixesAsync logic (e.g., adding a new diagnostic property check, changing fix-all behavior) must be applied to all 6 files identically. Bug fixes applied to one file are silently missed in the others.
- Bug Risk: High — a fix in one fixer is easily forgotten in the 5 others. Historical diffs show these files evolve in lock-step, creating repeated merge burden.
- Code Bloat: ~240 lines of redundant code across 6 files.
Refactoring Recommendations
- Introduce a
FixtureMethodSignatureFixerBase abstract base class
- Extract to:
src/Analyzers/MSTest.Analyzers.CodeFixes/Helpers/FixtureMethodSignatureFixerBase.cs
- The base class holds
GetFixAllProvider() and RegisterCodeFixesAsync() with abstract properties for DiagnosticRuleId, IsParameterLess, and ShouldBeStatic.
- Each existing fixer becomes a thin subclass with ~10 lines:
// Proposed base class
internal abstract class FixtureMethodSignatureFixerBase : CodeFixProvider
{
protected abstract string DiagnosticRuleId { get; }
protected abstract bool IsParameterLess { get; }
protected abstract bool ShouldBeStatic { get; }
public sealed override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create(DiagnosticRuleId);
public sealed override FixAllProvider GetFixAllProvider() =>
WellKnownFixAllProviders.BatchFixer;
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SyntaxNode root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
SyntaxNode node = root.FindNode(context.Span);
if (context.Diagnostics.Any(d => !d.Properties.ContainsKey(DiagnosticDescriptorHelper.CannotFixPropertyKey)))
{
context.RegisterCodeFix(
CodeAction.Create(
CodeFixResources.FixSignatureCodeFix,
ct => FixtureMethodFixer.FixSignatureAsync(context.Document, root, node, IsParameterLess, ShouldBeStatic, ct),
GetType().Name),
context.Diagnostics);
}
}
}
// Each fixer becomes trivial:
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(TestCleanupShouldBeValidFixer))]
[Shared]
public sealed class TestCleanupShouldBeValidFixer : FixtureMethodSignatureFixerBase
{
protected override string DiagnosticRuleId => DiagnosticIds.TestCleanupShouldBeValidRuleId;
protected override bool IsParameterLess => true;
protected override bool ShouldBeStatic => false;
}
- Reduce total file count from 6 ~49-line files to 1 ~35-line base class + 6 ~10-line leaf classes.
Implementation Checklist
Analysis Metadata
- Analyzed Files: 6 (AssemblyCleanupShouldBeValidFixer, AssemblyInitializeShouldBeValidFixer, ClassCleanupShouldBeValidFixer, ClassInitializeShouldBeValidFixer, TestCleanupShouldBeValidFixer, TestInitializeShouldBeValidFixer)
- Detection Method: Semantic code analysis + structural diff
- Commit: 756cdee
- Analysis Date: 2026-06-06
Generated by Duplicate Code Detector · sonnet46 3.8M · ◷
Add this agentic workflows to your repo
To install this agentic workflow, run
gh aw add githubnext/agentics/workflows/duplicate-code-detector.md@main
Analysis of commit 756cdee
Assignee:
@copilotSummary
Six code fixer classes in
src/Analyzers/MSTest.Analyzers.CodeFixes/are structurally identical (49 lines each), differing only in class name, diagnostic ID, and two boolean parameters (isParameterLess,shouldBeStatic). This is ~294 lines total with ~240+ lines of pure duplication.Duplication Details
Pattern: Identical
FixtureMethodFixer-based Code Fixerssrc/Analyzers/MSTest.Analyzers.CodeFixes/AssemblyCleanupShouldBeValidFixer.cs(lines 1–49)src/Analyzers/MSTest.Analyzers.CodeFixes/AssemblyInitializeShouldBeValidFixer.cs(lines 1–49)src/Analyzers/MSTest.Analyzers.CodeFixes/ClassCleanupShouldBeValidFixer.cs(lines 1–49)src/Analyzers/MSTest.Analyzers.CodeFixes/ClassInitializeShouldBeValidFixer.cs(lines 1–49)src/Analyzers/MSTest.Analyzers.CodeFixes/TestCleanupShouldBeValidFixer.cs(lines 1–49)src/Analyzers/MSTest.Analyzers.CodeFixes/TestInitializeShouldBeValidFixer.cs(lines 1–49)The only differences across the 6 files:
AssemblyCleanupShouldBeValidFixerAssemblyCleanupShouldBeValidRuleIdtruetrueAssemblyInitializeShouldBeValidFixerAssemblyInitializeShouldBeValidRuleIdfalsetrueClassCleanupShouldBeValidFixerClassCleanupShouldBeValidRuleIdtruetrueClassInitializeShouldBeValidFixerClassInitializeShouldBeValidRuleIdfalsetrueTestCleanupShouldBeValidFixerTestCleanupShouldBeValidRuleIdtruefalseTestInitializeShouldBeValidFixerTestInitializeShouldBeValidRuleIdtruefalseImpact Analysis
RegisterCodeFixesAsynclogic (e.g., adding a new diagnostic property check, changing fix-all behavior) must be applied to all 6 files identically. Bug fixes applied to one file are silently missed in the others.Refactoring Recommendations
FixtureMethodSignatureFixerBaseabstract base classsrc/Analyzers/MSTest.Analyzers.CodeFixes/Helpers/FixtureMethodSignatureFixerBase.csGetFixAllProvider()andRegisterCodeFixesAsync()with abstract properties forDiagnosticRuleId,IsParameterLess, andShouldBeStatic.Implementation Checklist
FixtureMethodSignatureFixerBaseinsrc/Analyzers/MSTest.Analyzers.CodeFixes/Helpers/[ExportCodeFixProvider]attribute is still on each leaf class (MEF requires it on the concrete type)Analysis Metadata
Add this agentic workflows to your repo
To install this agentic workflow, run