Skip to content

[duplicate-code] Duplicate Code: CommandLineProvider Boilerplate Structure #8544

@Evangelink

Description

@Evangelink

🔍 Duplicate Code Detected: CommandLineProvider Boilerplate Structure

Analysis of commit 372c5f1

Summary

Detected significant structural duplication across ICommandLineOptionsProvider implementations for diagnostic extensions (HangDump, CrashDump). These providers share nearly identical boilerplate for property implementations, option registration, and validation patterns.

Duplication Details

Pattern: CommandLineProvider Structure

  • Severity: Medium

  • Occurrences: 5+ instances

  • Locations:

    • src/Platform/Microsoft.Testing.Extensions.HangDump/HangDumpCommandLineProvider.cs (70 lines)
    • src/Platform/Microsoft.Testing.Extensions.CrashDump/CrashDumpCommandLineProvider.cs (68 lines)
    • src/Platform/Microsoft.Testing.Extensions.AzureDevOpsReport/AzureDevOpsCommandLineProvider.cs
    • src/Platform/Microsoft.Testing.Extensions.MSBuild/MSBuildCommandLineProvider.cs
    • src/Platform/Microsoft.Testing.Platform/CommandLine/PlatformCommandLineProvider.cs
  • Code Sample (HangDump):

    internal sealed class HangDumpCommandLineProvider : ICommandLineOptionsProvider
    {
        public const string HangDumpOptionName = "hangdump";
        public const string HangDumpFileNameOptionName = "hangdump-filename";
        public const string HangDumpTimeoutOptionName = "hangdump-timeout";
        public const string HangDumpTypeOptionName = "hangdump-type";
    
        private static readonly string[] HangDumpTypeOptions = ["Mini", "Heap", "Full", "Triage", "None"];
    
        private static readonly IReadOnlyCollection<CommandLineOption> CachedCommandLineOptions =
        [
            new(HangDumpOptionName, ExtensionResources.HangDumpOptionDescription, ArgumentArity.Zero, false),
            new(HangDumpTimeoutOptionName, ExtensionResources.HangDumpTimeoutOptionDescription, ArgumentArity.ExactlyOne, false),
            new(HangDumpFileNameOptionName, ExtensionResources.HangDumpFileNameOptionDescription, ArgumentArity.ExactlyOne, false),
            new(HangDumpTypeOptionName, ExtensionResources.HangDumpTypeOptionDescription, ArgumentArity.ExactlyOne, false)
        ];
    
        public string Uid => nameof(HangDumpCommandLineProvider);
        public string Version => ExtensionVersion.DefaultSemVer;
        public string DisplayName => ExtensionResources.HangDumpExtensionDisplayName;
        public string Description => ExtensionResources.HangDumpExtensionDescription;
    
        public Task<bool> IsEnabledAsync() => Task.FromResult(true);
    
        public IReadOnlyCollection<CommandLineOption> GetCommandLineOptions() => CachedCommandLineOptions;
    
        public Task<ValidationResult> ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments)
        {
            if (commandOption.Name == HangDumpTimeoutOptionName && !TimeSpanParser.TryParse(arguments[0], out TimeSpan _))
            {
                return ValidationResult.InvalidTask(ExtensionResources.HangDumpTimeoutOptionInvalidArgument);
            }
    
            if (commandOption.Name == HangDumpTypeOptionName)
            {
                if (!HangDumpTypeOptions.Contains(arguments[0], StringComparer.OrdinalIgnoreCase))
                {
                    return ValidationResult.InvalidTask(string.Format(CultureInfo.InvariantCulture, 
                        ExtensionResources.HangDumpTypeOptionInvalidType, arguments[0]));
                }
            }
    
            return ValidationResult.ValidTask;
        }
    
        public Task<ValidationResult> ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions)
            => (commandLineOptions.IsOptionSet(HangDumpTimeoutOptionName) ||
                commandLineOptions.IsOptionSet(HangDumpFileNameOptionName) ||
                commandLineOptions.IsOptionSet(HangDumpTypeOptionName)) &&
                !commandLineOptions.IsOptionSet(HangDumpOptionName)
                ? ValidationResult.InvalidTask(ExtensionResources.MissingHangDumpMainOption)
                : ValidationResult.ValidTask;
    }
  • Code Sample (CrashDump - similar structure):

    internal sealed class CrashDumpCommandLineProvider : ICommandLineOptionsProvider
    {
        private static readonly string[] DumpTypeOptions = ["Mini", "Heap", "Triage", "Full"];
        private static readonly IReadOnlyCollection<CommandLineOption> CachedCommandLineOptions =
        [
            new(CrashDumpCommandLineOptions.CrashDumpOptionName, CrashDumpResources.CrashDumpOptionDescription, ArgumentArity.Zero, false),
            new(CrashDumpCommandLineOptions.CrashReportOptionName, CrashDumpResources.CrashReportOptionDescription, ArgumentArity.Zero, false),
            new(CrashDumpCommandLineOptions.CrashDumpFileNameOptionName, CrashDumpResources.CrashDumpFileNameOptionDescription, ArgumentArity.ExactlyOne, false),
            new(CrashDumpCommandLineOptions.CrashDumpTypeOptionName, CrashDumpResources.CrashDumpTypeOptionDescription, ArgumentArity.ExactlyOne, false)
        ];
    
        public string Uid => nameof(CrashDumpCommandLineProvider);
        public string Version => ExtensionVersion.DefaultSemVer;
        public string DisplayName => CrashDumpResources.CrashDumpDisplayName;
        public string Description => CrashDumpResources.CrashDumpDescription;
    
        public Task<bool> IsEnabledAsync() => Task.FromResult(true);
    
        public IReadOnlyCollection<CommandLineOption> GetCommandLineOptions() => CachedCommandLineOptions;
    
        public Task<ValidationResult> ValidateOptionArgumentsAsync(CommandLineOption commandOption, string[] arguments)
        {
            if (commandOption.Name == CrashDumpCommandLineOptions.CrashDumpTypeOptionName)
            {
                if (!DumpTypeOptions.Contains(arguments[0], StringComparer.OrdinalIgnoreCase))
                {
                    return ValidationResult.InvalidTask(string.Format(CultureInfo.InvariantCulture, 
                        CrashDumpResources.CrashDumpTypeOptionInvalidType, arguments[0]));
                }
            }
            return ValidationResult.ValidTask;
        }
    
        public Task<ValidationResult> ValidateCommandLineOptionsAsync(ICommandLineOptions commandLineOptions)
            => IsCrashDumpMainOptionMissing(commandLineOptions)
                ? ValidationResult.InvalidTask(CrashDumpResources.MissingCrashDumpMainOption)
                : IsCrashReportUnsupportedOnCurrentPlatform(commandLineOptions)
                ? ValidationResult.InvalidTask(CrashDumpResources.CrashReportNotSupportedOnWindowsErrorMessage)
                : ValidationResult.ValidTask;
    }

Impact Analysis

  • Maintainability: Boilerplate property implementations repeated across providers
  • Bug Risk: Validation logic patterns duplicated with minor variations
  • Code Bloat: ~300+ lines of similar structural code
  • Consistency: Difficult to ensure uniform validation behavior

Refactoring Recommendations

Option 1: Abstract Base Class (Recommended)

Create a base class that handles common provider patterns:

public abstract class ExtensionCommandLineProviderBase : ICommandLineOptionsProvider
{
    private readonly IReadOnlyCollection<CommandLineOption> _cachedOptions;
    
    protected ExtensionCommandLineProviderBase(IReadOnlyCollection<CommandLineOption> options)
    {
        _cachedOptions = options;
    }
    
    public abstract string Uid { get; }
    public abstract string DisplayName { get; }
    public abstract string Description { get; }
    
    public virtual string Version => ExtensionVersion.DefaultSemVer;
    public virtual Task<bool> IsEnabledAsync() => Task.FromResult(true);
    public IReadOnlyCollection<CommandLineOption> GetCommandLineOptions() => _cachedOptions;
    
    public virtual Task<ValidationResult> ValidateOptionArgumentsAsync(
        CommandLineOption commandOption, string[] arguments)
        => ValidationResult.ValidTask;
    
    public virtual Task<ValidationResult> ValidateCommandLineOptionsAsync(
        ICommandLineOptions commandLineOptions)
        => ValidationResult.ValidTask;
}

// Usage:
internal sealed class HangDumpCommandLineProvider : ExtensionCommandLineProviderBase
{
    private static readonly string[] HangDumpTypeOptions = ["Mini", "Heap", "Full", "Triage", "None"];
    
    public HangDumpCommandLineProvider() : base(
    [
        new("hangdump", ExtensionResources.HangDumpOptionDescription, ArgumentArity.Zero, false),
        new("hangdump-timeout", ExtensionResources.HangDumpTimeoutOptionDescription, ArgumentArity.ExactlyOne, false),
        new("hangdump-filename", ExtensionResources.HangDumpFileNameOptionDescription, ArgumentArity.ExactlyOne, false),
        new("hangdump-type", ExtensionResources.HangDumpTypeOptionDescription, ArgumentArity.ExactlyOne, false)
    ])
    {
    }
    
    public override string Uid => nameof(HangDumpCommandLineProvider);
    public override string DisplayName => ExtensionResources.HangDumpExtensionDisplayName;
    public override string Description => ExtensionResources.HangDumpExtensionDescription;
    
    public override Task<ValidationResult> ValidateOptionArgumentsAsync(
        CommandLineOption commandOption, string[] arguments)
    {
        // Only validation-specific logic here
    }
}

Benefits:

  • Eliminates ~60% of boilerplate
  • Centralizes common interface implementation
  • Maintains type safety
  • Easier to add new providers

Estimated effort: 4-6 hours

Option 2: Fluent Builder Pattern

Create a fluent builder for constructing command-line providers:

public class CommandLineProviderBuilder
{
    public CommandLineProviderBuilder WithOption(string name, string description, ArgumentArity arity)
        => /* ... */;
    
    public CommandLineProviderBuilder WithValidation(Func<CommandLineOption, string[], ValidationResult> validator)
        => /* ... */;
    
    public ICommandLineOptionsProvider Build() => /* ... */;
}

Benefits:

  • Declarative provider construction
  • Reduces duplication

Drawbacks:

  • Less discoverable
  • More complex implementation

Estimated effort: 6-8 hours

Implementation Checklist

  • Review duplication findings
  • Choose refactoring approach (recommend Option 1)
  • Create abstract base class
  • Refactor HangDumpCommandLineProvider
  • Refactor CrashDumpCommandLineProvider
  • Update other providers as needed
  • Update unit tests
  • Verify command-line parsing still works correctly
  • Run integration tests

Analysis Metadata

  • Analyzed Files: 895 C# source files
  • Detection Method: Semantic code analysis + interface implementation pattern matching
  • Commit: 372c5f1
  • Analysis Date: 2026-05-25T05:45:53.799+00:00
  • Pattern Type: Structural duplication (interface implementation boilerplate)

Generated by Duplicate Code Detector · ● 1.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 May 27, 2026, 5:53 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