🔍 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
-
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
-
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
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
🔍 Duplicate Code Detected:
*ReportGeneratorCommandLineStructural PatternAnalysis of commit cf37d3b
Assignee:
@copilotSummary
Three
*ReportGeneratorCommandLinefiles (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
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):Impact Analysis
ArgumentArity, renamingDiscoverTestsOptionKey) must be applied to all three files. The validation delegation toReportFileNameValidatoris already factored out, but the call-site scaffolding is still repeated.Refactoring Recommendations
Extract a
ReportCommandLineOptionsProviderBasebase class or factory inSharedExtensionHelpersLower-effort: at minimum add a code comment linking all three files to improve discoverability and remind contributors to keep them in sync.
Implementation Checklist
ReportCommandLineOptionsProviderBaseis worthwhile given the small absolute size of each fileSharedExtensionHelpers--helpand--infoacceptance test expectations still passAnalysis Metadata
CtrfReportGeneratorCommandLine.cs,JUnitReportGeneratorCommandLine.cs,HtmlReportGeneratorCommandLine.cs)Add this agentic workflows to your repo
To install this agentic workflow, run