Skip to content

[duplicate-code] Duplicate Code: Near-Identical Fixture Method Code Fixers in MSTest.Analyzers.CodeFixes #8873

@Evangelink

Description

@Evangelink

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

  1. 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;
}
  1. Reduce total file count from 6 ~49-line files to 1 ~35-line base class + 6 ~10-line leaf classes.

Implementation Checklist

  • Review duplication findings
  • Create FixtureMethodSignatureFixerBase in src/Analyzers/MSTest.Analyzers.CodeFixes/Helpers/
  • Refactor all 6 fixer classes to extend the base class
  • Verify [ExportCodeFixProvider] attribute is still on each leaf class (MEF requires it on the concrete type)
  • Run MSTest.Analyzers unit tests to confirm no regressions
  • Update tests if test infrastructure needs updating

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
  • expires on Jun 8, 2026, 5:37 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