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
-
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.
-
Refactor JUnitReportGeneratorCommandLine.ValidateOptionArgumentsAsync
- Replace ~30 lines of inline validation with a single call to
ReportFileNameValidator.ValidateReportFileNameArgumentAsync(arguments, ".xml", ...).
-
Refactor JUnitReportGeneratorCommandLine.ValidateCommandLineOptionsAsync
- Replace the inline ternary chain with a call to
ReportFileNameValidator.ValidateReportCommandLineOptionsAsync(...).
-
Remove private duplicates
- Delete
DirectorySeparators field, EscapesResultsDirectory, and IsPathFullyQualified from JUnitReportGeneratorCommandLine.cs.
Implementation Checklist
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
Analysis of commit 1b64756
Assignee:
@copilotSummary
JUnitReportGeneratorCommandLine.cscontains a private copy of the path-validation logic already provided by the sharedReportFileNameValidatorhelper. The same duplication does not exist inTrxReportGeneratorCommandLine.csorHtmlReportGeneratorCommandLine.cs— both of those files were already refactored to delegate toReportFileNameValidator. The JUnit command-line provider was created independently and the refactoring was never applied to it.Duplication Details
Pattern: Private Copies of
EscapesResultsDirectoryandIsPathFullyQualifiedsrc/Platform/Microsoft.Testing.Extensions.JUnitReport/JUnitReportGeneratorCommandLine.cs(lines 17, 70–92, 94–129) — private copiessrc/Platform/SharedExtensionHelpers/ReportFileNameValidator.cs(lines 12, 57–77, 79–114) — authoritative sourcePattern: Inline Validation Duplicating
ValidateReportFileNameArgumentAsyncJUnitReportGeneratorCommandLine.ValidateOptionArgumentsAsync(lines 32–61) contains ~30 lines of inline validation that is semantically identical toReportFileNameValidator.ValidateReportFileNameArgumentAsync. The HTML and TRX equivalents delegate in a single expression:ValidateCommandLineOptionsAsyncis also duplicated inline instead of delegating toReportFileNameValidator.ValidateReportCommandLineOptionsAsync(matching the TRX and HTML providers).Root Cause
JUnitReport.csprojdoes not link anySharedExtensionHelperssource files. Compare:HtmlReport.csproj: linksReportFileNameValidator.cs,ReportFileNameSanitizer.cs,ReportFileNameHelper.cs,TargetFrameworkMonikerHelper.cs,ReportFileWriterHelper.cs✅TrxReport.csproj: linksReportFileNameValidator.cs,ReportFileNameSanitizer.cs,ReportFileNameHelper.cs,ReportFileWriterHelper.cs✅JUnitReport.csproj: links none of these ❌Impact Analysis
ReportFileNameValidatormust now be applied in two places to cover the JUnit extension. TheEscapesResultsDirectoryandIsPathFullyQualifiedlogic is security-relevant (it prevents directory-traversal file writes) — a divergence here could silently leave the JUnit path validation incomplete.ReportFileNameValidatorever gains additional validation (new edge cases, new reserved characters, etc.), the JUnit inline copy will not inherit those fixes automatically.Refactoring Recommendations
Add
SharedExtensionHelpersfile links toJUnitReport.csproj<Compile Include="...SharedExtensionHelpers\ReportFileNameValidator.cs" Link="Helpers\ReportFileNameValidator.cs" />tosrc/Platform/Microsoft.Testing.Extensions.JUnitReport/Microsoft.Testing.Extensions.JUnitReport.csprojHtmlReport.csprojandTrxReport.csproj.Refactor
JUnitReportGeneratorCommandLine.ValidateOptionArgumentsAsyncReportFileNameValidator.ValidateReportFileNameArgumentAsync(arguments, ".xml", ...).Refactor
JUnitReportGeneratorCommandLine.ValidateCommandLineOptionsAsyncReportFileNameValidator.ValidateReportCommandLineOptionsAsync(...).Remove private duplicates
DirectorySeparatorsfield,EscapesResultsDirectory, andIsPathFullyQualifiedfromJUnitReportGeneratorCommandLine.cs.Implementation Checklist
SharedExtensionHelpers\ReportFileNameValidator.cs<Compile>link toJUnitReport.csprojValidateOptionArgumentsAsyncto delegate toReportFileNameValidator.ValidateReportFileNameArgumentAsyncValidateCommandLineOptionsAsyncto delegate toReportFileNameValidator.ValidateReportCommandLineOptionsAsyncDirectorySeparators,EscapesResultsDirectory, andIsPathFullyQualifiedfromJUnitReportGeneratorCommandLine.csnetstandard2.0+ supported .NET versions)Analysis Metadata
JUnitReportGeneratorCommandLine.cs,HtmlReportGeneratorCommandLine.cs,TrxCommandLine.cs,ReportFileNameValidator.cs,JUnitReport.csproj,HtmlReport.csproj,TrxReport.csprojAdd this agentic workflows to your repo
To install this agentic workflow, run