Skip to content

[duplicate-code] Duplicate Code: Identical structural pattern across CtrfReportGeneratorCommandLine, JUnitReportGeneratorCommandLine, HtmlReportG [Content truncated due to length] #9132

@Evangelink

Description

@Evangelink

🔍 Duplicate Code Detected: *ReportGeneratorCommandLine Structural Pattern

Analysis of commit cf37d3b

Assignee: @copilot

Summary

Three *ReportGeneratorCommandLine files (CtrfReportGeneratorCommandLine, JUnitReportGeneratorCommandLine, HtmlReportGeneratorCommandLine) are structurally identical ~48-line classes that differ only in their option-name constants, file-extension strings, and resource-string references. Both the constructor base-call pattern and the two validation override methods delegate to the same shared utilities (ReportFileNameValidator) with the same shape, repeated three times.

Duplication Details

Pattern: Identical class structure across three command-line option providers

  • Severity: Low–Medium
  • Occurrences: 3
  • Locations:
    • src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportGeneratorCommandLine.cs (all 48 lines)
    • src/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportGeneratorCommandLine.cs (all 48 lines)
    • src/Platform/Microsoft.Testing.Extensions.HtmlReport/HtmlReportGeneratorCommandLine.cs (all 48 lines)

Structural template shared by all three (pseudocode, X = Ctrf/JUnit/Html, ext = .json/.xml/.html):

internal sealed class {X}ReportGeneratorCommandLine : CommandLineOptionsProviderBase
{
    public const string {X}ReportOptionName = "report-{x}";
    public const string {X}ReportFileNameOptionName = "report-{x}-filename";

    public {X}ReportGeneratorCommandLine()
        : base(
            nameof({X}ReportGeneratorCommandLine),
            ExtensionVersion.DefaultSemVer,
            ExtensionResources.{X}ReportGeneratorDisplayName,
            ExtensionResources.{X}ReportGeneratorDescription,
            [
                new({X}ReportOptionName, ExtensionResources.{X}ReportOptionDescription, ArgumentArity.Zero, false),
                new({X}ReportFileNameOptionName, ExtensionResources.{X}ReportFileNameOptionDescription, ArgumentArity.ExactlyOne, false),
            ])
    { }

    public override Task<ValidationResult> ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments)
        => commandOption.Name == {X}ReportFileNameOptionName
            ? ReportFileNameValidator.ValidateReportFileNameArgumentAsync(
                arguments,
                "{ext}",
                ExtensionResources.{X}ReportFileNameMustNotBeEmpty,
                ExtensionResources.{X}ReportFileNameExtensionIsNot{Ext},
                ExtensionResources.{X}ReportFileNameRelativePathMustStayUnderResultsDirectory)
            : ValidationResult.ValidTask;

    public override Task<ValidationResult> ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions)
        => ReportFileNameValidator.ValidateReportCommandLineOptionsAsync(
            commandLineOptions,
            {X}ReportOptionName,
            {X}ReportFileNameOptionName,
            ExtensionResources.{X}ReportFileNameRequires{X}Report,
            ExtensionResources.{X}ReportIsNotValidForDiscovery,
            PlatformCommandLineProvider.DiscoverTestsOptionKey);
}

Impact Analysis

  • Maintainability: Any structural change to the two-option pattern (e.g., adding a third option, changing ArgumentArity, renaming DiscoverTestsOptionKey) must be applied to all three files. The validation delegation to ReportFileNameValidator is already factored out, but the call-site scaffolding is still repeated.
  • Bug Risk: Low for existing code; higher for future report formats added by copy-paste without reviewing the pattern.
  • Code Bloat: ~144 lines across three files where ~30 lines per file (the two validation methods) are mechanically identical in structure.

Refactoring Recommendations

  1. Extract a ReportCommandLineOptionsProviderBase base class or factory in SharedExtensionHelpers

    • Create a shared base (or a static factory) that accepts the option names, file extension, and resource strings as constructor parameters, encapsulating the two validation overrides:
      // Conceptual — in SharedExtensionHelpers
      internal abstract class ReportCommandLineOptionsProviderBase : CommandLineOptionsProviderBase
      {
          private readonly string _fileNameOptionName;
          private readonly string _fileExtension;
          // resource string fields...
      
          protected ReportCommandLineOptionsProviderBase(
              string displayName, string description,
              string reportOptionName, string fileNameOptionName,
              string fileExtension,
              /* resource strings */)
              : base(displayName, ExtensionVersion.DefaultSemVer, description, description,
                     [ new(reportOptionName, ..., ArgumentArity.Zero, false),
                       new(fileNameOptionName, ..., ArgumentArity.ExactlyOne, false) ]) { ... }
      
          public override Task<ValidationResult> ValidateOptionArgumentsAsync(...) { /* shared impl */ }
          public override Task<ValidationResult> ValidateCommandLineOptionsAsync(...) { /* shared impl */ }
      }
    • Each concrete class becomes a 5–10 line declaration of constants + constructor.
    • Estimated effort: 2–3 hours
    • Benefits: a fourth report format requires only one small class; structural changes propagate automatically
  2. Lower-effort: at minimum add a code comment linking all three files to improve discoverability and remind contributors to keep them in sync.

    • Estimated effort: < 10 minutes

Implementation Checklist

  • Review duplication findings
  • Assess whether a shared ReportCommandLineOptionsProviderBase is worthwhile given the small absolute size of each file
  • If extracting: move the two validation overrides to the shared base in SharedExtensionHelpers
  • Update all three concrete classes to derive from the new base
  • Verify --help and --info acceptance test expectations still pass
  • Verify no functionality broken

Analysis Metadata

  • Analyzed Files: 3 (CtrfReportGeneratorCommandLine.cs, JUnitReportGeneratorCommandLine.cs, HtmlReportGeneratorCommandLine.cs)
  • Detection Method: Structural comparison of peer files
  • Commit: cf37d3b
  • Analysis Date: 2026-06-15

🤖 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. · 979.9 AIC · ⌖ 14.3 AIC · [◷]( · )

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 17, 2026, 6:14 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