< Summary

Information
Class: DotNetApiDiff.Commands.CompareCommandSettings
Assembly: DotNetApiDiff
File(s): /home/runner/work/dotnet-api-diff/dotnet-api-diff/src/DotNetApiDiff/Commands/CompareCommand.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 12
Coverable lines: 12
Total lines: 573
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_SourceAssemblyPath()100%210%
get_TargetAssemblyPath()100%210%
get_ConfigFile()100%210%
get_OutputFormat()100%210%
get_OutputFile()100%210%
get_NamespaceFilters()100%210%
get_ExcludePatterns()100%210%
get_TypePatterns()100%210%
get_IncludeInternals()100%210%
get_IncludeCompilerGenerated()100%210%
get_NoColor()100%210%
get_Verbose()100%210%

File(s)

/home/runner/work/dotnet-api-diff/dotnet-api-diff/src/DotNetApiDiff/Commands/CompareCommand.cs

#LineLine coverage
 1// Copyright DotNet API Diff Project Contributors - SPDX Identifier: MIT
 2using DotNetApiDiff.Interfaces;
 3using DotNetApiDiff.Models;
 4using DotNetApiDiff.Models.Configuration;
 5using Microsoft.Extensions.DependencyInjection;
 6using Microsoft.Extensions.Logging;
 7using Spectre.Console;
 8using Spectre.Console.Cli;
 9using System.ComponentModel;
 10using System.Diagnostics.CodeAnalysis;
 11using System.Reflection;
 12
 13namespace DotNetApiDiff.Commands;
 14
 15/// <summary>
 16/// Settings for the compare command
 17/// </summary>
 18[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicPro
 19public class CompareCommandSettings : CommandSettings
 20{
 21    [CommandArgument(0, "<sourceAssembly>")]
 22    [Description("Path to the source/baseline assembly")]
 023    public string? SourceAssemblyPath { get; set; }
 24
 25    [CommandArgument(1, "<targetAssembly>")]
 26    [Description("Path to the target/current assembly")]
 027    public string? TargetAssemblyPath { get; set; }
 28
 29    [CommandOption("-c|--config <configFile>")]
 30    [Description("Path to configuration file")]
 031    public string? ConfigFile { get; set; }
 32
 33    [CommandOption("-o|--output <format>")]
 34    [Description("Output format (console, json, html, markdown)")]
 035    public string? OutputFormat { get; set; }
 36
 37    [CommandOption("-p|--output-file <path>")]
 38    [Description("Output file path (required for json, html, markdown formats)")]
 039    public string? OutputFile { get; set; }
 40
 41    [CommandOption("-f|--filter <namespace>")]
 42    [Description("Filter to specific namespaces (can be specified multiple times)")]
 043    public string[]? NamespaceFilters { get; set; }
 44
 45    [CommandOption("-e|--exclude <pattern>")]
 46    [Description("Exclude types matching pattern (can be specified multiple times)")]
 047    public string[]? ExcludePatterns { get; set; }
 48
 49    [CommandOption("-t|--type <pattern>")]
 50    [Description("Filter to specific type patterns (can be specified multiple times)")]
 051    public string[]? TypePatterns { get; set; }
 52
 53    [CommandOption("--include-internals")]
 54    [Description("Include internal types in the comparison")]
 55    [DefaultValue(false)]
 056    public bool IncludeInternals { get; set; }
 57
 58    [CommandOption("--include-compiler-generated")]
 59    [Description("Include compiler-generated types in the comparison")]
 60    [DefaultValue(false)]
 061    public bool IncludeCompilerGenerated { get; set; }
 62
 63    [CommandOption("--no-color")]
 64    [Description("Disable colored output")]
 65    [DefaultValue(false)]
 066    public bool NoColor { get; set; }
 67
 68    [CommandOption("-v|--verbose")]
 69    [Description("Enable verbose output")]
 70    [DefaultValue(false)]
 071    public bool Verbose { get; set; }
 72}
 73
 74/// <summary>
 75/// Command to compare two assemblies
 76/// </summary>
 77public class CompareCommand : Command<CompareCommandSettings>
 78{
 79    private readonly IServiceProvider _serviceProvider;
 80    private readonly ILogger<CompareCommand> _logger;
 81    private readonly IExitCodeManager _exitCodeManager;
 82    private readonly IGlobalExceptionHandler _exceptionHandler;
 83
 84    /// <summary>
 85    /// Initializes a new instance of the <see cref="CompareCommand"/> class.
 86    /// </summary>
 87    /// <param name="serviceProvider">The service provider.</param>
 88    /// <param name="logger">The logger.</param>
 89    /// <param name="exitCodeManager">The exit code manager.</param>
 90    /// <param name="exceptionHandler">The global exception handler.</param>
 91    public CompareCommand(
 92        IServiceProvider serviceProvider,
 93        ILogger<CompareCommand> logger,
 94        IExitCodeManager exitCodeManager,
 95        IGlobalExceptionHandler exceptionHandler)
 96    {
 97        _serviceProvider = serviceProvider;
 98        _logger = logger;
 99        _exitCodeManager = exitCodeManager;
 100        _exceptionHandler = exceptionHandler;
 101    }
 102
 103    /// <summary>
 104    /// Validates the command settings
 105    /// </summary>
 106    /// <param name="context">The command context</param>
 107    /// <param name="settings">The command settings</param>
 108    /// <returns>ValidationResult indicating success or failure</returns>
 109    public override ValidationResult Validate([NotNull] CommandContext context, [NotNull] CompareCommandSettings setting
 110    {
 111        // Validate source assembly path
 112        if (string.IsNullOrEmpty(settings.SourceAssemblyPath))
 113        {
 114            return ValidationResult.Error("Source assembly path is required");
 115        }
 116
 117        if (!File.Exists(settings.SourceAssemblyPath))
 118        {
 119            return ValidationResult.Error($"Source assembly file not found: {settings.SourceAssemblyPath}");
 120        }
 121
 122        // Validate target assembly path
 123        if (string.IsNullOrEmpty(settings.TargetAssemblyPath))
 124        {
 125            return ValidationResult.Error("Target assembly path is required");
 126        }
 127
 128        if (!File.Exists(settings.TargetAssemblyPath))
 129        {
 130            return ValidationResult.Error($"Target assembly file not found: {settings.TargetAssemblyPath}");
 131        }
 132
 133        // Validate config file if specified
 134        if (!string.IsNullOrEmpty(settings.ConfigFile) && !File.Exists(settings.ConfigFile))
 135        {
 136            return ValidationResult.Error($"Configuration file not found: {settings.ConfigFile}");
 137        }
 138
 139        // Validate output format if provided
 140        if (!string.IsNullOrEmpty(settings.OutputFormat))
 141        {
 142            string format = settings.OutputFormat.ToLowerInvariant();
 143            if (format != "console" && format != "json" && format != "html" && format != "markdown")
 144            {
 145                return ValidationResult.Error($"Invalid output format: {settings.OutputFormat}. Valid formats are: conso
 146            }
 147
 148            // Validate output file requirements
 149            if (format == "html")
 150            {
 151                // HTML format requires an output file
 152                if (string.IsNullOrEmpty(settings.OutputFile))
 153                {
 154                    return ValidationResult.Error($"Output file is required for {settings.OutputFormat} format. Use --ou
 155                }
 156
 157                // Validate output directory exists
 158                var outputDir = Path.GetDirectoryName(settings.OutputFile);
 159                if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
 160                {
 161                    return ValidationResult.Error($"Output directory does not exist: {outputDir}");
 162                }
 163            }
 164        }
 165        else if (!string.IsNullOrEmpty(settings.OutputFile))
 166        {
 167            // If output file is specified for non-HTML formats, validate the directory exists
 168            var outputDir = Path.GetDirectoryName(settings.OutputFile);
 169            if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
 170            {
 171                return ValidationResult.Error($"Output directory does not exist: {outputDir}");
 172            }
 173        }
 174
 175        return ValidationResult.Success();
 176    }
 177
 178    /// <summary>
 179    /// Executes the command
 180    /// </summary>
 181    /// <param name="context">The command context</param>
 182    /// <param name="settings">The command settings</param>
 183    /// <returns>Exit code (0 for success, non-zero for failure)</returns>
 184    public override int Execute([NotNull] CommandContext context, [NotNull] CompareCommandSettings settings)
 185    {
 186        try
 187        {
 188            // Create a logging scope for this command execution
 189            using (_logger.BeginScope("Compare command execution"))
 190            {
 191                // Set up logging level based on verbose flag
 192                if (settings.Verbose)
 193                {
 194                    _logger.LogInformation("Verbose logging enabled");
 195                }
 196
 197                // Configure console output
 198                if (settings.NoColor)
 199                {
 200                    _logger.LogDebug("Disabling colored output");
 201                    AnsiConsole.Profile.Capabilities.ColorSystem = ColorSystem.NoColors;
 202                }
 203
 204                // Load configuration
 205                ComparisonConfiguration config;
 206                if (!string.IsNullOrEmpty(settings.ConfigFile))
 207                {
 208                    using (_logger.BeginScope("Configuration loading"))
 209                    {
 210                        _logger.LogInformation("Loading configuration from {ConfigFile}", settings.ConfigFile);
 211
 212                        try
 213                        {
 214                            // Verify the file exists and is accessible
 215                            if (!File.Exists(settings.ConfigFile))
 216                            {
 217                                throw new FileNotFoundException($"Configuration file not found: {settings.ConfigFile}", 
 218                            }
 219
 220                            // Try to load the configuration
 221                            config = ComparisonConfiguration.LoadFromJsonFile(settings.ConfigFile);
 222                            _logger.LogInformation("Configuration loaded successfully");
 223                        }
 224                        catch (Exception ex)
 225                        {
 226                            _logger.LogError(ex, "Error loading configuration from {ConfigFile}", settings.ConfigFile);
 227                            AnsiConsole.MarkupLine($"[red]Error loading configuration:[/] {ex.Message}");
 228
 229                            // Use the ExitCodeManager to determine the appropriate exit code for errors
 230                            return _exitCodeManager.GetExitCodeForException(ex);
 231                        }
 232                    }
 233                }
 234                else
 235                {
 236                    _logger.LogInformation("Using default configuration");
 237                    config = ComparisonConfiguration.CreateDefault();
 238                }
 239
 240                // Apply command-line filters and options
 241                ApplyCommandLineOptions(settings, config);
 242
 243                // NOW CREATE THE COMMAND-SPECIFIC CONTAINER
 244                _logger.LogInformation("Creating command-specific service container with loaded configuration");
 245
 246                var commandServices = new ServiceCollection();
 247
 248                // Reuse shared services from root container
 249                var loggerFactory = _serviceProvider.GetRequiredService<ILoggerFactory>();
 250                commandServices.AddSingleton(loggerFactory);
 251                commandServices.AddLogging(); // This adds ILogger<T> services
 252
 253                // Add the loaded configuration
 254                commandServices.AddSingleton(config);                // Add all business logic services with configurati
 255                commandServices.AddScoped<IAssemblyLoader, AssemblyLoading.AssemblyLoader>();
 256                commandServices.AddScoped<IApiExtractor, ApiExtraction.ApiExtractor>();
 257                commandServices.AddScoped<IMemberSignatureBuilder, ApiExtraction.MemberSignatureBuilder>();
 258                commandServices.AddScoped<ITypeAnalyzer, ApiExtraction.TypeAnalyzer>();
 259                commandServices.AddScoped<IDifferenceCalculator, ApiExtraction.DifferenceCalculator>();
 260                commandServices.AddScoped<IReportGenerator, Reporting.ReportGenerator>();
 261
 262                // Add configuration-specific services
 263                commandServices.AddScoped<INameMapper>(provider =>
 264                {
 265                    return new ApiExtraction.NameMapper(
 266                        config.Mappings,
 267                        loggerFactory.CreateLogger<ApiExtraction.NameMapper>());
 268                });
 269
 270                commandServices.AddScoped<IChangeClassifier>(provider =>
 271                    new ApiExtraction.ChangeClassifier(
 272                        config.BreakingChangeRules,
 273                        config.Exclusions,
 274                        loggerFactory.CreateLogger<ApiExtraction.ChangeClassifier>()));
 275
 276                // Add the main comparison service that depends on configured services
 277                commandServices.AddScoped<IApiComparer>(provider =>
 278                    new ApiExtraction.ApiComparer(
 279                        provider.GetRequiredService<IApiExtractor>(),
 280                        provider.GetRequiredService<IDifferenceCalculator>(),
 281                        provider.GetRequiredService<INameMapper>(),
 282                        provider.GetRequiredService<IChangeClassifier>(),
 283                        config,
 284                        provider.GetRequiredService<ILogger<ApiExtraction.ApiComparer>>()));
 285
 286                // Execute the command with the configured services
 287                using (var commandProvider = commandServices.BuildServiceProvider())
 288                {
 289                    return ExecuteWithConfiguredServices(settings, config, commandProvider);
 290                }
 291            }
 292        }
 293        catch (Exception ex)
 294        {
 295            // Use our centralized exception handler for any unhandled exceptions
 296            return _exceptionHandler.HandleException(ex, "Compare command execution");
 297        }
 298    }
 299
 300    /// <summary>
 301    /// Executes the comparison logic using the configured services
 302    /// </summary>
 303    /// <param name="settings">Command settings</param>
 304    /// <param name="config">Loaded configuration</param>
 305    /// <param name="serviceProvider">Command-specific service provider</param>
 306    /// <returns>Exit code</returns>
 307    private int ExecuteWithConfiguredServices(CompareCommandSettings settings, ComparisonConfiguration config, IServiceP
 308    {
 309        // Load assemblies
 310        Assembly sourceAssembly;
 311        Assembly targetAssembly;
 312
 313        using (_logger.BeginScope("Assembly loading"))
 314        {
 315            _logger.LogInformation("Loading source assembly: {Path}", settings.SourceAssemblyPath);
 316            _logger.LogInformation("Loading target assembly: {Path}", settings.TargetAssemblyPath);
 317
 318            var assemblyLoader = serviceProvider.GetRequiredService<IAssemblyLoader>();
 319
 320            try
 321            {
 322                sourceAssembly = assemblyLoader.LoadAssembly(settings.SourceAssemblyPath!);
 323            }
 324            catch (Exception ex)
 325            {
 326                _logger.LogError(ex, "Failed to load source assembly: {Path}", settings.SourceAssemblyPath);
 327                AnsiConsole.MarkupLine($"[red]Error loading source assembly:[/] {ex.Message}");
 328
 329                return _exitCodeManager.GetExitCodeForException(ex);
 330            }
 331
 332            try
 333            {
 334                targetAssembly = assemblyLoader.LoadAssembly(settings.TargetAssemblyPath!);
 335            }
 336            catch (Exception ex)
 337            {
 338                _logger.LogError(ex, "Failed to load target assembly: {Path}", settings.TargetAssemblyPath);
 339                AnsiConsole.MarkupLine($"[red]Error loading target assembly:[/] {ex.Message}");
 340
 341                return _exitCodeManager.GetExitCodeForException(ex);
 342            }
 343        }
 344
 345        // Extract API information
 346        using (_logger.BeginScope("API extraction"))
 347        {
 348            _logger.LogInformation("Extracting API information from assemblies");
 349            var apiExtractor = serviceProvider.GetRequiredService<IApiExtractor>();
 350
 351            // Pass the filter configuration to the API extractor
 352            var sourceApi = apiExtractor.ExtractApiMembers(sourceAssembly, config.Filters);
 353            var targetApi = apiExtractor.ExtractApiMembers(targetAssembly, config.Filters);
 354
 355            // Log the number of API members extracted
 356            _logger.LogInformation(
 357                "Extracted {SourceCount} API members from source and {TargetCount} API members from target",
 358                sourceApi.Count(),
 359                targetApi.Count());
 360        }
 361
 362        // Compare APIs
 363        Models.ComparisonResult comparisonResult;
 364        using (_logger.BeginScope("API comparison"))
 365        {
 366            _logger.LogInformation("Comparing APIs");
 367            var apiComparer = serviceProvider.GetRequiredService<IApiComparer>();
 368
 369            try
 370            {
 371                // Use the single CompareAssemblies method - configuration is now injected into dependencies
 372                comparisonResult = apiComparer.CompareAssemblies(sourceAssembly, targetAssembly);
 373
 374                // Update configuration with actual command-line values ONLY if explicitly provided by user
 375                if (!string.IsNullOrEmpty(settings.OutputFormat) && Enum.TryParse<ReportFormat>(settings.OutputFormat, t
 376                {
 377                    comparisonResult.Configuration.OutputFormat = outputFormat;
 378                }
 379
 380                if (!string.IsNullOrEmpty(settings.OutputFile))
 381                {
 382                    comparisonResult.Configuration.OutputPath = settings.OutputFile;
 383                }
 384            }
 385            catch (Exception ex)
 386            {
 387                _logger.LogError(ex, "Error comparing assemblies");
 388                AnsiConsole.MarkupLine($"[red]Error comparing assemblies:[/] {ex.Message}");
 389
 390                return _exitCodeManager.GetExitCodeForException(ex);
 391            }
 392        }
 393
 394        // Create ApiComparison from ComparisonResult
 395        var comparison = CreateApiComparisonFromResult(comparisonResult);
 396
 397        // Generate report
 398        using (_logger.BeginScope("Report generation"))
 399        {
 400            // Use the configuration from the comparison result, which now has the correct precedence applied
 401            var effectiveFormat = comparisonResult.Configuration.OutputFormat;
 402            var effectiveOutputFile = comparisonResult.Configuration.OutputPath;
 403
 404            _logger.LogInformation("Generating {Format} report", effectiveFormat);
 405            var reportGenerator = serviceProvider.GetRequiredService<IReportGenerator>();
 406
 407            string report;
 408            try
 409            {
 410                if (string.IsNullOrEmpty(effectiveOutputFile))
 411                {
 412                    // No output file specified - output to console regardless of format
 413                    report = reportGenerator.GenerateReport(comparisonResult, effectiveFormat);
 414
 415                    // Output the formatted report to the console
 416                    // Use Console.Write to avoid format string interpretation issues
 417                    Console.Write(report);
 418                }
 419                else
 420                {
 421                    // Output file specified - save to the specified file
 422                    reportGenerator.SaveReportAsync(comparisonResult, effectiveFormat, effectiveOutputFile).GetAwaiter()
 423                    _logger.LogInformation("Report saved to {OutputFile}", effectiveOutputFile);
 424                }
 425            }
 426            catch (Exception ex)
 427            {
 428                _logger.LogError(ex, "Error generating {Format} report", effectiveFormat);
 429                AnsiConsole.MarkupLine($"[red]Error generating report:[/] {ex.Message}");
 430
 431                return _exitCodeManager.GetExitCodeForException(ex);
 432            }
 433        }
 434
 435        // Use the ExitCodeManager to determine the appropriate exit code
 436        int exitCode = _exitCodeManager.GetExitCode(comparison);
 437
 438        if (comparison.HasBreakingChanges)
 439        {
 440            _logger.LogWarning("{Count} breaking changes detected", comparison.BreakingChangesCount);
 441        }
 442        else
 443        {
 444            _logger.LogInformation("Comparison completed successfully with no breaking changes");
 445        }
 446
 447        _logger.LogInformation(
 448            "Exiting with code {ExitCode}: {Description}",
 449            exitCode,
 450            _exitCodeManager.GetExitCodeDescription(exitCode));
 451
 452        return exitCode;
 453    }
 454
 455    /// <summary>
 456    /// Applies command-line options to the configuration
 457    /// </summary>
 458    /// <param name="settings">Command settings</param>
 459    /// <param name="config">Configuration to update</param>
 460    /// <param name="logger">Logger for diagnostic information</param>
 461    private void ApplyCommandLineOptions(CompareCommandSettings settings, Models.Configuration.ComparisonConfiguration c
 462    {
 463        using (_logger.BeginScope("Applying command-line options"))
 464        {
 465            // Apply namespace filters if specified
 466            if (settings.NamespaceFilters != null && settings.NamespaceFilters.Length > 0)
 467            {
 468                _logger.LogInformation("Applying namespace filters: {Filters}", string.Join(", ", settings.NamespaceFilt
 469
 470                // Add namespace filters to the configuration
 471                config.Filters.IncludeNamespaces.AddRange(settings.NamespaceFilters);
 472
 473                // If we have explicit includes, we're filtering to only those namespaces
 474                if (config.Filters.IncludeNamespaces.Count > 0)
 475                {
 476                    _logger.LogInformation("Filtering to only include specified namespaces");
 477                }
 478            }
 479
 480            // Apply type pattern filters if specified
 481            if (settings.TypePatterns != null && settings.TypePatterns.Length > 0)
 482            {
 483                _logger.LogInformation("Applying type pattern filters: {Patterns}", string.Join(", ", settings.TypePatte
 484
 485                // Add type pattern filters to the configuration
 486                config.Filters.IncludeTypes.AddRange(settings.TypePatterns);
 487
 488                _logger.LogInformation("Filtering to only include types matching specified patterns");
 489            }
 490
 491            // Apply command-line exclusions if specified
 492            if (settings.ExcludePatterns != null && settings.ExcludePatterns.Length > 0)
 493            {
 494                _logger.LogInformation("Applying exclusion patterns: {Patterns}", string.Join(", ", settings.ExcludePatt
 495
 496                // Add exclusion patterns to the configuration
 497                foreach (var pattern in settings.ExcludePatterns)
 498                {
 499                    // Determine if this is a namespace or type pattern based on presence of dot
 500                    if (pattern.Contains('.'))
 501                    {
 502                        // Assume it's a type pattern if it contains a dot
 503                        config.Exclusions.ExcludedTypePatterns.Add(pattern);
 504                    }
 505                    else
 506                    {
 507                        // Otherwise assume it's a namespace pattern
 508                        config.Filters.ExcludeNamespaces.Add(pattern);
 509                    }
 510                }
 511            }
 512
 513            // Apply internal types inclusion if specified
 514            if (settings.IncludeInternals)
 515            {
 516                _logger.LogInformation("Including internal types in comparison");
 517                config.Filters.IncludeInternals = true;
 518            }
 519
 520            // Apply compiler-generated types inclusion if specified
 521            if (settings.IncludeCompilerGenerated)
 522            {
 523                _logger.LogInformation("Including compiler-generated types in comparison");
 524                config.Filters.IncludeCompilerGenerated = true;
 525            }
 526        }
 527    }
 528
 529    /// <summary>
 530    /// Creates an ApiComparison object from a ComparisonResult
 531    /// </summary>
 532    /// <param name="comparisonResult">The comparison result to convert</param>
 533    /// <returns>An ApiComparison object</returns>
 534    private Models.ApiComparison CreateApiComparisonFromResult(Models.ComparisonResult comparisonResult)
 535    {
 536        return new Models.ApiComparison
 537        {
 538            Additions = comparisonResult.Differences
 539                .Where(d => d.ChangeType == Models.ChangeType.Added)
 540                .Select(d => new Models.ApiChange
 541                {
 542                    Type = Models.ChangeType.Added,
 543                    TargetMember = new Models.ApiMember { Name = d.ElementName },
 544                    IsBreakingChange = d.IsBreakingChange
 545                }).ToList(),
 546            Removals = comparisonResult.Differences
 547                .Where(d => d.ChangeType == Models.ChangeType.Removed)
 548                .Select(d => new Models.ApiChange
 549                {
 550                    Type = Models.ChangeType.Removed,
 551                    SourceMember = new Models.ApiMember { Name = d.ElementName },
 552                    IsBreakingChange = d.IsBreakingChange
 553                }).ToList(),
 554            Modifications = comparisonResult.Differences
 555                .Where(d => d.ChangeType == Models.ChangeType.Modified)
 556                .Select(d => new Models.ApiChange
 557                {
 558                    Type = Models.ChangeType.Modified,
 559                    SourceMember = new Models.ApiMember { Name = d.ElementName },
 560                    TargetMember = new Models.ApiMember { Name = d.ElementName },
 561                    IsBreakingChange = d.IsBreakingChange
 562                }).ToList(),
 563            Excluded = comparisonResult.Differences
 564                .Where(d => d.ChangeType == Models.ChangeType.Excluded)
 565                .Select(d => new Models.ApiChange
 566                {
 567                    Type = Models.ChangeType.Excluded,
 568                    SourceMember = new Models.ApiMember { Name = d.ElementName },
 569                    IsBreakingChange = false
 570                }).ToList()
 571        };
 572    }
 573}