Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/DotNetApiDiff/ApiExtraction/ApiComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Reflection;
using DotNetApiDiff.Interfaces;
using DotNetApiDiff.Models;
using DotNetApiDiff.Models.Configuration;
using Microsoft.Extensions.Logging;

namespace DotNetApiDiff.ApiExtraction;
Expand All @@ -15,6 +16,7 @@ public class ApiComparer : IApiComparer
private readonly IDifferenceCalculator _differenceCalculator;
private readonly INameMapper _nameMapper;
private readonly IChangeClassifier _changeClassifier;
private readonly ComparisonConfiguration _configuration;
private readonly ILogger<ApiComparer> _logger;

/// <summary>
Expand All @@ -24,18 +26,21 @@ public class ApiComparer : IApiComparer
/// <param name="differenceCalculator">Calculator for detailed change analysis</param>
/// <param name="nameMapper">Mapper for namespace and type name transformations</param>
/// <param name="changeClassifier">Classifier for breaking changes and exclusions</param>
/// <param name="configuration">Configuration used for the comparison</param>
/// <param name="logger">Logger for diagnostic information</param>
public ApiComparer(
IApiExtractor apiExtractor,
IDifferenceCalculator differenceCalculator,
INameMapper nameMapper,
IChangeClassifier changeClassifier,
ComparisonConfiguration configuration,
ILogger<ApiComparer> logger)
{
_apiExtractor = apiExtractor ?? throw new ArgumentNullException(nameof(apiExtractor));
_differenceCalculator = differenceCalculator ?? throw new ArgumentNullException(nameof(differenceCalculator));
_nameMapper = nameMapper ?? throw new ArgumentNullException(nameof(nameMapper));
_changeClassifier = changeClassifier ?? throw new ArgumentNullException(nameof(changeClassifier));
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

Expand Down Expand Up @@ -66,7 +71,8 @@ public ComparisonResult CompareAssemblies(Assembly oldAssembly, Assembly newAsse
{
OldAssemblyPath = oldAssembly.Location,
NewAssemblyPath = newAssembly.Location,
ComparisonTimestamp = DateTime.UtcNow
ComparisonTimestamp = DateTime.UtcNow,
Configuration = _configuration
};

try
Expand Down
9 changes: 8 additions & 1 deletion src/DotNetApiDiff/Commands/CompareCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,14 @@ public override int Execute([NotNull] CommandContext context, [NotNull] CompareC
loggerFactory.CreateLogger<ApiExtraction.ChangeClassifier>()));

// Add the main comparison service that depends on configured services
commandServices.AddScoped<IApiComparer, ApiExtraction.ApiComparer>();
commandServices.AddScoped<IApiComparer>(provider =>
new ApiExtraction.ApiComparer(
provider.GetRequiredService<IApiExtractor>(),
provider.GetRequiredService<IDifferenceCalculator>(),
provider.GetRequiredService<INameMapper>(),
provider.GetRequiredService<IChangeClassifier>(),
config,
provider.GetRequiredService<ILogger<ApiExtraction.ApiComparer>>()));

// Execute the command with the configured services
using (var commandProvider = commandServices.BuildServiceProvider())
Expand Down
2 changes: 1 addition & 1 deletion src/DotNetApiDiff/DotNetApiDiff.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<PackageReference Include="Spectre.Console" Version="0.48.0" />
<PackageReference Include="Spectre.Console.Cli" Version="0.48.0" />
<PackageReference Include="System.Reflection.Metadata" Version="8.0.1" />
<PackageReference Include="Scriban" Version="5.9.0" />
<PackageReference Include="Scriban" Version="6.2.1" />
</ItemGroup>

<ItemGroup>
Expand Down
66 changes: 41 additions & 25 deletions src/DotNetApiDiff/Reporting/HtmlFormatterScriban.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using DotNetApiDiff.Models.Configuration;
using Scriban;
using Scriban.Runtime;
using System.Linq;

namespace DotNetApiDiff.Reporting;

Expand Down Expand Up @@ -65,42 +66,57 @@ public string Format(ComparisonResult result)

private object PrepareConfigData(ComparisonConfiguration config)
{
var namespaceMappings = config?.Mappings?.NamespaceMappings ?? new Dictionary<string, List<string>>();

// Convert Dictionary to array of objects with key/value properties for Scriban
var namespaceMappingsArray = namespaceMappings.Select(kvp => new { key = kvp.Key, value = kvp.Value }).ToList();
var typeMappingsArray = (config?.Mappings?.TypeMappings ?? new Dictionary<string, string>()).Select(kvp => new { key = kvp.Key, value = kvp.Value }).ToList();

var mappingsResult = new
{
namespace_mappings = namespaceMappingsArray,
type_mappings = typeMappingsArray,
auto_map_same_name_types = config?.Mappings?.AutoMapSameNameTypes ?? false,
ignore_case = config?.Mappings?.IgnoreCase ?? false
};

return new
{
filters = new
{
include_internals = config.Filters.IncludeInternals,
include_compiler_generated = config.Filters.IncludeCompilerGenerated,
include_namespaces = config.Filters.IncludeNamespaces?.ToList() ?? new List<string>(),
exclude_namespaces = config.Filters.ExcludeNamespaces?.ToList() ?? new List<string>(),
include_types = config.Filters.IncludeTypes?.ToList() ?? new List<string>(),
exclude_types = config.Filters.ExcludeTypes?.ToList() ?? new List<string>()
},
mappings = new
{
namespace_mappings = config.Mappings.NamespaceMappings ?? new Dictionary<string, List<string>>(),
type_mappings = config.Mappings.TypeMappings ?? new Dictionary<string, string>(),
auto_map_same_name_types = config.Mappings.AutoMapSameNameTypes,
ignore_case = config.Mappings.IgnoreCase
include_internals = config?.Filters?.IncludeInternals ?? false,
include_compiler_generated = config?.Filters?.IncludeCompilerGenerated ?? false,
include_namespaces = config?.Filters?.IncludeNamespaces?.ToList() ?? new List<string>(),
exclude_namespaces = config?.Filters?.ExcludeNamespaces?.ToList() ?? new List<string>(),
include_types = config?.Filters?.IncludeTypes?.ToList() ?? new List<string>(),
exclude_types = config?.Filters?.ExcludeTypes?.ToList() ?? new List<string>()
},
mappings = mappingsResult,
exclusions = new
{
excluded_types = config.Exclusions.ExcludedTypes?.ToList() ?? new List<string>(),
excluded_members = config.Exclusions.ExcludedMembers?.ToList() ?? new List<string>(),
excluded_type_patterns = config.Exclusions.ExcludedTypePatterns?.ToList() ?? new List<string>(),
excluded_member_patterns = config.Exclusions.ExcludedMemberPatterns?.ToList() ?? new List<string>(),
exclude_compiler_generated = config.Exclusions.ExcludeCompilerGenerated,
exclude_obsolete = config.Exclusions.ExcludeObsolete
excluded_types = config?.Exclusions?.ExcludedTypes?.ToList() ?? new List<string>(),
excluded_members = config?.Exclusions?.ExcludedMembers?.ToList() ?? new List<string>(),
excluded_type_patterns = config?.Exclusions?.ExcludedTypePatterns?.ToList() ?? new List<string>(),
excluded_member_patterns = config?.Exclusions?.ExcludedMemberPatterns?.ToList() ?? new List<string>(),
exclude_compiler_generated = config?.Exclusions?.ExcludeCompilerGenerated ?? false,
exclude_obsolete = config?.Exclusions?.ExcludeObsolete ?? false
},
breaking_change_rules = new
{
treat_type_removal_as_breaking = config.BreakingChangeRules.TreatTypeRemovalAsBreaking,
treat_member_removal_as_breaking = config.BreakingChangeRules.TreatMemberRemovalAsBreaking,
treat_signature_change_as_breaking = config.BreakingChangeRules.TreatSignatureChangeAsBreaking,
treat_reduced_accessibility_as_breaking = config.BreakingChangeRules.TreatReducedAccessibilityAsBreaking
treat_type_removal_as_breaking = config?.BreakingChangeRules?.TreatTypeRemovalAsBreaking ?? true,
treat_member_removal_as_breaking = config?.BreakingChangeRules?.TreatMemberRemovalAsBreaking ?? true,
treat_signature_change_as_breaking = config?.BreakingChangeRules?.TreatSignatureChangeAsBreaking ?? true,
treat_reduced_accessibility_as_breaking = config?.BreakingChangeRules?.TreatReducedAccessibilityAsBreaking ?? true,
treat_added_type_as_breaking = config?.BreakingChangeRules?.TreatAddedTypeAsBreaking ?? false,
treat_added_member_as_breaking = config?.BreakingChangeRules?.TreatAddedMemberAsBreaking ?? false,
treat_added_interface_as_breaking = config?.BreakingChangeRules?.TreatAddedInterfaceAsBreaking ?? true,
treat_removed_interface_as_breaking = config?.BreakingChangeRules?.TreatRemovedInterfaceAsBreaking ?? true,
treat_parameter_name_change_as_breaking = config?.BreakingChangeRules?.TreatParameterNameChangeAsBreaking ?? false,
treat_added_optional_parameter_as_breaking = config?.BreakingChangeRules?.TreatAddedOptionalParameterAsBreaking ?? false
},
output_format = config.OutputFormat.ToString(),
output_path = config.OutputPath ?? string.Empty
output_format = config?.OutputFormat.ToString() ?? "Console",
output_path = config?.OutputPath ?? string.Empty,
fail_on_breaking_changes = config?.FailOnBreakingChanges ?? false
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div class="config-item">
<span class="config-label">{{ $1 }}:</span>
<span class="config-value">{{ $2 | format_boolean }}</span>
</div>
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div class="config-item">
<span class="config-label">{{ label }}:</span>
{{if mappings && mappings.size > 0}}
<span class="config-label">{{ $2 }}:</span>
{{if $1 && $1.size > 0}}
<div class="config-mappings">
{{for mapping in mappings}}
{{for mapping in $1}}
<div class="config-mapping">
<span class="mapping-from">{{ mapping.key }}</span>
<span class="mapping-arrow">→</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div class="config-item">
<span class="config-label">{{ label }}:</span>
{{if mappings && mappings.size > 0}}
<span class="config-label">{{ $2 }}:</span>
{{if $1 && $1.size > 0}}
<div class="config-mappings">
{{for mapping in mappings}}
{{for mapping in $1}}
<div class="config-mapping">
<span class="mapping-from">{{ mapping.key }}</span>
<span class="mapping-arrow">→</span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<div class="config-item">
<span class="config-label">{{ label }}:</span>
{{if items && items.size > 0}}
<span class="config-label">{{ $2 }}:</span>
{{if $1 && $1.size > 0}}
<div class="config-list">
{{for item in items}}
{{for item in $1}}
<span class="config-list-item">{{ item }}</span>
{{end}}
</div>
Expand Down
59 changes: 23 additions & 36 deletions src/DotNetApiDiff/Reporting/HtmlTemplates/configuration.scriban
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@
<!-- Filters Section -->
<div class="config-section">
<h3>🔍 Filters</h3>
<div class="config-item">
<span class="config-label">Include Internals:</span>
<span class="config-value">{{ config.filters.include_internals | format_boolean }}</span>
</div>
<div class="config-item">
<span class="config-label">Include Compiler Generated:</span>
<span class="config-value">{{ config.filters.include_compiler_generated | format_boolean }}</span>
</div>
{{ include "config-boolean-item" "Include Internals" config.filters.include_internals }}
{{ include "config-boolean-item" "Include Compiler Generated" config.filters.include_compiler_generated }}
{{ include "config-string-list" config.filters.include_namespaces "Include Namespaces" }}
{{ include "config-string-list" config.filters.exclude_namespaces "Exclude Namespaces" }}
{{ include "config-string-list" config.filters.include_types "Include Types" }}
Expand All @@ -19,14 +13,8 @@
<!-- Mappings Section -->
<div class="config-section">
<h3>🔗 Mappings</h3>
<div class="config-item">
<span class="config-label">Auto Map Same Name Types:</span>
<span class="config-value">{{ config.mappings.auto_map_same_name_types | format_boolean }}</span>
</div>
<div class="config-item">
<span class="config-label">Ignore Case:</span>
<span class="config-value">{{ config.mappings.ignore_case | format_boolean }}</span>
</div>
{{ include "config-boolean-item" "Auto Map Same Name Types" config.mappings.auto_map_same_name_types }}
{{ include "config-boolean-item" "Ignore Case" config.mappings.ignore_case }}
{{ include "config-mappings" config.mappings.type_mappings "Type Mappings" }}
{{ include "config-namespace-mappings" config.mappings.namespace_mappings "Namespace Mappings" }}
</div>
Expand All @@ -38,31 +26,33 @@
{{ include "config-string-list" config.exclusions.excluded_members "Excluded Members" }}
{{ include "config-string-list" config.exclusions.excluded_type_patterns "Excluded Type Patterns" }}
{{ include "config-string-list" config.exclusions.excluded_member_patterns "Excluded Member Patterns" }}
{{ include "config-boolean-item" "Exclude Compiler Generated" config.exclusions.exclude_compiler_generated }}
{{ include "config-boolean-item" "Exclude Obsolete" config.exclusions.exclude_obsolete }}
</div>

<!-- Breaking Change Rules Section -->
<div class="config-section breaking-rules">
<h3>⚠️ Breaking Change Rules</h3>
<div class="config-grid-inner">
<div>
<div class="config-item">
<span class="config-label">Treat Type Removal as Breaking:</span>
<span class="config-value">{{ config.breaking_change_rules.treat_type_removal_as_breaking | format_boolean }}</span>
</div>
<div class="config-item">
<span class="config-label">Treat Member Removal as Breaking:</span>
<span class="config-value">{{ config.breaking_change_rules.treat_member_removal_as_breaking | format_boolean }}</span>
</div>
<h4>Removal Rules</h4>
{{ include "config-boolean-item" "Treat Type Removal as Breaking" config.breaking_change_rules.treat_type_removal_as_breaking }}
{{ include "config-boolean-item" "Treat Member Removal as Breaking" config.breaking_change_rules.treat_member_removal_as_breaking }}

<h4>Addition Rules</h4>
{{ include "config-boolean-item" "Treat Added Type as Breaking" config.breaking_change_rules.treat_added_type_as_breaking }}
{{ include "config-boolean-item" "Treat Added Member as Breaking" config.breaking_change_rules.treat_added_member_as_breaking }}
</div>
<div>
<div class="config-item">
<span class="config-label">Treat Signature Change as Breaking:</span>
<span class="config-value">{{ config.breaking_change_rules.treat_signature_change_as_breaking | format_boolean }}</span>
</div>
<div class="config-item">
<span class="config-label">Treat Reduced Accessibility as Breaking:</span>
<span class="config-value">{{ config.breaking_change_rules.treat_reduced_accessibility_as_breaking | format_boolean }}</span>
</div>
<h4>Modification Rules</h4>
{{ include "config-boolean-item" "Treat Signature Change as Breaking" config.breaking_change_rules.treat_signature_change_as_breaking }}
{{ include "config-boolean-item" "Treat Reduced Accessibility as Breaking" config.breaking_change_rules.treat_reduced_accessibility_as_breaking }}
{{ include "config-boolean-item" "Treat Parameter Name Change as Breaking" config.breaking_change_rules.treat_parameter_name_change_as_breaking }}
{{ include "config-boolean-item" "Treat Added Optional Parameter as Breaking" config.breaking_change_rules.treat_added_optional_parameter_as_breaking }}

<h4>Interface Rules</h4>
{{ include "config-boolean-item" "Treat Added Interface as Breaking" config.breaking_change_rules.treat_added_interface_as_breaking }}
{{ include "config-boolean-item" "Treat Removed Interface as Breaking" config.breaking_change_rules.treat_removed_interface_as_breaking }}
</div>
</div>
</div>
Expand All @@ -74,10 +64,7 @@
<span class="config-label">Output Format:</span>
<span class="config-value">{{ config.output_format }}</span>
</div>
<div class="config-item">
<span class="config-label">Fail On Breaking Changes:</span>
<span class="config-value">{{ config.fail_on_breaking_changes | format_boolean }}</span>
</div>
{{ include "config-boolean-item" "Fail On Breaking Changes" config.fail_on_breaking_changes }}
{{if config.output_path}}
<div class="config-item">
<span class="config-label">Output Path:</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
</button>
</div>
<div id="config-details" class="config-details" style="display: none;">
{{ include "configuration" result.configuration }}
{{ include "configuration" }}
</div>
</section>

Expand Down
9 changes: 9 additions & 0 deletions src/DotNetApiDiff/Reporting/HtmlTemplates/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,15 @@ section h2 {
font-size: 1.2rem;
}

.config-section h4 {
margin: 15px 0 10px 0;
color: #6c757d;
font-size: 1rem;
font-weight: 600;
border-bottom: 1px solid #e9ecef;
padding-bottom: 5px;
}

.config-item {
margin-bottom: 12px;
}
Expand Down
2 changes: 2 additions & 0 deletions tests/DotNetApiDiff.Tests/ApiExtraction/ApiComparerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using DotNetApiDiff.ApiExtraction;
using DotNetApiDiff.Interfaces;
using DotNetApiDiff.Models;
using DotNetApiDiff.Models.Configuration;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
Expand Down Expand Up @@ -35,6 +36,7 @@ public ApiComparerTests()
_mockDifferenceCalculator.Object,
_mockNameMapper.Object,
_mockChangeClassifier.Object,
ComparisonConfiguration.CreateDefault(),
_mockLogger.Object);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ public void CompareTypes_WithNoMapping_FindsDifferences()
_differenceCalculatorMock.Object,
nameMapper,
_changeClassifierMock.Object,
ComparisonConfiguration.CreateDefault(),
_loggerMock.Object);

var addedDifference = new ApiDifference { ChangeType = ChangeType.Added };
Expand Down
Loading