Skip to content

[duplicate-code] Duplicate Code: JUnitReportGeneratorCommandLine Duplicates ReportFileNameValidator Logic #8946

@Evangelink

Description

@Evangelink

Analysis of commit 1b64756

Assignee: @copilot

Summary

JUnitReportGeneratorCommandLine.cs contains a private copy of the path-validation logic already provided by the shared ReportFileNameValidator helper. The same duplication does not exist in TrxReportGeneratorCommandLine.cs or HtmlReportGeneratorCommandLine.cs — both of those files were already refactored to delegate to ReportFileNameValidator. The JUnit command-line provider was created independently and the refactoring was never applied to it.

Duplication Details

Pattern: Private Copies of EscapesResultsDirectory and IsPathFullyQualified

  • Severity: Medium
  • Occurrences: 3 instances (field + 2 methods duplicated)
  • Locations:
    • src/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportGeneratorCommandLine.cs (lines 17, 70–92, 94–129) — private copies
    • src/Platform/SharedExtensionHelpers/ReportFileNameValidator.cs (lines 12, 57–77, 79–114) — authoritative source
  • Code Sample (duplicate in JUnit):
// JUnitReportGeneratorCommandLine.cs (lines 70–129)
private static readonly char[] DirectorySeparators = [Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar];

private static bool EscapesResultsDirectory(string path)
{
    if (IsPathFullyQualified(path))
        return false;
    if (Path.IsPathRooted(path))
        return true;
    return path.Split(DirectorySeparators, StringSplitOptions.RemoveEmptyEntries).Any(segment => segment == "..");
}

private static bool IsPathFullyQualified(string path)
{
#if NETCOREAPP
    return Path.IsPathFullyQualified(path);
#else
    // ... 25 lines of identical .NET Framework fallback ...
#endif
}

Pattern: Inline Validation Duplicating ValidateReportFileNameArgumentAsync

JUnitReportGeneratorCommandLine.ValidateOptionArgumentsAsync (lines 32–61) contains ~30 lines of inline validation that is semantically identical to ReportFileNameValidator.ValidateReportFileNameArgumentAsync. The HTML and TRX equivalents delegate in a single expression:

// HtmlReportGeneratorCommandLine.cs — correct (delegates to shared helper)
public override Task<ValidationResult> ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments)
    => commandOption.Name == HtmlReportFileNameOptionName
        ? ReportFileNameValidator.ValidateReportFileNameArgumentAsync(arguments, ".html", ...)
        : ValidationResult.ValidTask;

// JUnitReportGeneratorCommandLine.cs — DUPLICATE (inline copy, ~30 lines)
public override Task<ValidationResult> ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments)
{
    if (commandOption.Name == JUnitReportFileNameOptionName)
    {
        if (arguments.Length is 0)
            return ValidationResult.InvalidTask(...);
        string argument = arguments[0];
        string fileNamePart = Path.GetFileName(argument);
        if (RoslynString.IsNullOrWhiteSpace(fileNamePart))
            return ValidationResult.InvalidTask(...);
        if (!fileNamePart.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
            return ValidationResult.InvalidTask(...);
        if (EscapesResultsDirectory(argument))
            return ValidationResult.InvalidTask(...);
    }
    return ValidationResult.ValidTask;
}

ValidateCommandLineOptionsAsync is also duplicated inline instead of delegating to ReportFileNameValidator.ValidateReportCommandLineOptionsAsync (matching the TRX and HTML providers).

Root Cause

JUnitReport.csproj does not link any SharedExtensionHelpers source files. Compare:

  • HtmlReport.csproj: links ReportFileNameValidator.cs, ReportFileNameSanitizer.cs, ReportFileNameHelper.cs, TargetFrameworkMonikerHelper.cs, ReportFileWriterHelper.cs
  • TrxReport.csproj: links ReportFileNameValidator.cs, ReportFileNameSanitizer.cs, ReportFileNameHelper.cs, ReportFileWriterHelper.cs
  • JUnitReport.csproj: links none of these ❌

Impact Analysis

  • Maintainability: A bug fix or behavioral change in ReportFileNameValidator must now be applied in two places to cover the JUnit extension. The EscapesResultsDirectory and IsPathFullyQualified logic is security-relevant (it prevents directory-traversal file writes) — a divergence here could silently leave the JUnit path validation incomplete.
  • Bug Risk: If ReportFileNameValidator ever gains additional validation (new edge cases, new reserved characters, etc.), the JUnit inline copy will not inherit those fixes automatically.
  • Code Bloat: ~60 lines of duplicated code that serve no purpose other than replicating an existing shared abstraction.

Refactoring Recommendations

  1. Add SharedExtensionHelpers file links to JUnitReport.csproj

    • Add <Compile Include="...SharedExtensionHelpers\ReportFileNameValidator.cs" Link="Helpers\ReportFileNameValidator.cs" /> to src/Platform/Microsoft.Testing.Extensions.JUnitReport/Microsoft.Testing.Extensions.JUnitReport.csproj
    • This matches the pattern used by both HtmlReport.csproj and TrxReport.csproj.
  2. Refactor JUnitReportGeneratorCommandLine.ValidateOptionArgumentsAsync

    • Replace ~30 lines of inline validation with a single call to ReportFileNameValidator.ValidateReportFileNameArgumentAsync(arguments, ".xml", ...).
  3. Refactor JUnitReportGeneratorCommandLine.ValidateCommandLineOptionsAsync

    • Replace the inline ternary chain with a call to ReportFileNameValidator.ValidateReportCommandLineOptionsAsync(...).
  4. Remove private duplicates

    • Delete DirectorySeparators field, EscapesResultsDirectory, and IsPathFullyQualified from JUnitReportGeneratorCommandLine.cs.

Implementation Checklist

  • Add SharedExtensionHelpers\ReportFileNameValidator.cs <Compile> link to JUnitReport.csproj
  • Refactor ValidateOptionArgumentsAsync to delegate to ReportFileNameValidator.ValidateReportFileNameArgumentAsync
  • Refactor ValidateCommandLineOptionsAsync to delegate to ReportFileNameValidator.ValidateReportCommandLineOptionsAsync
  • Remove DirectorySeparators, EscapesResultsDirectory, and IsPathFullyQualified from JUnitReportGeneratorCommandLine.cs
  • Verify build succeeds for all target frameworks (netstandard2.0 + supported .NET versions)
  • Run existing JUnit report tests to confirm no behavioral regressions

Analysis Metadata

  • Analyzed Files: JUnitReportGeneratorCommandLine.cs, HtmlReportGeneratorCommandLine.cs, TrxCommandLine.cs, ReportFileNameValidator.cs, JUnitReport.csproj, HtmlReport.csproj, TrxReport.csproj
  • Detection Method: Semantic code analysis — cross-file method signature and body comparison
  • Commit: 1b64756
  • Analysis Date: 2026-06-09

Generated by Duplicate Code Detector · sonnet46 3.6M ·

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 11, 2026, 5:45 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