< Summary

Information
Class: DotNetApiDiff.ApiExtraction.ApiExtractor
Assembly: DotNetApiDiff
File(s): /home/runner/work/dotnet-api-diff/dotnet-api-diff/src/DotNetApiDiff/ApiExtraction/ApiExtractor.cs
Line coverage
31%
Covered lines: 60
Uncovered lines: 129
Coverable lines: 189
Total lines: 354
Line coverage: 31.7%
Branch coverage
26%
Covered branches: 13
Total branches: 50
Branch coverage: 26%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%44100%
ExtractApiMembers(...)41.66%291250.79%
ExtractTypeMembers(...)100%2265%
GetPublicTypes(...)18.18%3432212.82%
IsTypeMatchingPattern(...)0%620%
IsCompilerGeneratedType(...)0%7280%

File(s)

/home/runner/work/dotnet-api-diff/dotnet-api-diff/src/DotNetApiDiff/ApiExtraction/ApiExtractor.cs

#LineLine coverage
 1// Copyright DotNet API Diff Project Contributors - SPDX Identifier: MIT
 2
 3using System.Reflection;
 4using DotNetApiDiff.Interfaces;
 5using DotNetApiDiff.Models;
 6using Microsoft.Extensions.Logging;
 7
 8namespace DotNetApiDiff.ApiExtraction;
 9
 10/// <summary>
 11/// Extracts public API members from .NET assemblies using reflection
 12/// </summary>
 13public class ApiExtractor : IApiExtractor
 14{
 15    private readonly ITypeAnalyzer _typeAnalyzer;
 16    private readonly ILogger<ApiExtractor> _logger;
 17
 18    /// <summary>
 19    /// Creates a new instance of the ApiExtractor
 20    /// </summary>
 21    /// <param name="typeAnalyzer">Type analyzer for detailed member analysis</param>
 22    /// <param name="logger">Logger for diagnostic information</param>
 1323    public ApiExtractor(ITypeAnalyzer typeAnalyzer, ILogger<ApiExtractor> logger)
 1324    {
 1325        _typeAnalyzer = typeAnalyzer ?? throw new ArgumentNullException(nameof(typeAnalyzer));
 1326        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 1327    }
 28
 29    /// <summary>
 30    /// Extracts all public API members from the specified assembly
 31    /// </summary>
 32    /// <param name="assembly">Assembly to extract API members from</param>
 33    /// <param name="filterConfig">Optional filter configuration to apply</param>
 34    /// <returns>Collection of public API members</returns>
 35    public IEnumerable<ApiMember> ExtractApiMembers(
 36        Assembly assembly,
 37        Models.Configuration.FilterConfiguration? filterConfig = null)
 238    {
 239        if (assembly == null)
 140        {
 141            throw new ArgumentNullException(nameof(assembly));
 42        }
 43
 144        _logger.LogInformation("Extracting API members from assembly: {AssemblyName}", assembly.GetName().Name);
 45
 146        if (filterConfig != null)
 047        {
 048            _logger.LogInformation(
 049                "Applying filter configuration with {IncludeCount} includes and {ExcludeCount} excludes",
 050                filterConfig.IncludeNamespaces.Count + filterConfig.IncludeTypes.Count,
 051                filterConfig.ExcludeNamespaces.Count + filterConfig.ExcludeTypes.Count);
 052        }
 53
 154        var apiMembers = new List<ApiMember>();
 55
 56        try
 157        {
 58            // Get all public types from the assembly, applying filters if provided
 159            var types = GetPublicTypes(assembly, filterConfig).ToList();
 160            _logger.LogDebug(
 161                "Found {TypeCount} public types in assembly {AssemblyName} after filtering",
 162                types.Count,
 163                assembly.GetName().Name);
 64
 65            // Process each type
 766            foreach (var type in types)
 267            {
 68                try
 269                {
 70                    // Add the type itself
 271                    var typeMember = _typeAnalyzer.AnalyzeType(type);
 272                    apiMembers.Add(typeMember);
 73
 74                    // Add all members of the type
 275                    var typeMembers = ExtractTypeMembers(type).ToList();
 276                    apiMembers.AddRange(typeMembers);
 77
 278                    _logger.LogDebug(
 279                        "Extracted {MemberCount} members from type {TypeName}",
 280                        typeMembers.Count,
 281                        type.FullName);
 282                }
 083                catch (Exception ex)
 084                {
 085                    _logger.LogError(ex, "Error extracting members from type {TypeName}", type.FullName);
 086                }
 287            }
 88
 189            _logger.LogInformation(
 190                "Extracted {MemberCount} total API members from assembly {AssemblyName}",
 191                apiMembers.Count,
 192                assembly.GetName().Name);
 93
 194            return apiMembers;
 95        }
 096        catch (ReflectionTypeLoadException ex)
 097        {
 098            _logger.LogError(ex, "Error loading types from assembly {AssemblyName}", assembly.GetName().Name);
 99
 100            // Log the loader exceptions for more detailed diagnostics
 0101            if (ex.LoaderExceptions != null)
 0102            {
 0103                foreach (var loaderEx in ex.LoaderExceptions)
 0104                {
 0105                    if (loaderEx != null)
 0106                    {
 0107                        _logger.LogError(loaderEx, "Loader exception: {Message}", loaderEx.Message);
 0108                    }
 0109                }
 0110            }
 111
 112            // Return any types that were successfully loaded
 0113            return apiMembers;
 114        }
 0115        catch (Exception ex)
 0116        {
 0117            _logger.LogError(
 0118                ex,
 0119                "Error extracting API members from assembly {AssemblyName}",
 0120                assembly.GetName().Name);
 0121            return apiMembers;
 122        }
 1123    }
 124
 125    /// <summary>
 126    /// Extracts public API members from a specific type
 127    /// </summary>
 128    /// <param name="type">Type to extract members from</param>
 129    /// <returns>Collection of public API members for the type</returns>
 130    public IEnumerable<ApiMember> ExtractTypeMembers(Type type)
 4131    {
 4132        if (type == null)
 1133        {
 1134            throw new ArgumentNullException(nameof(type));
 135        }
 136
 3137        var members = new List<ApiMember>();
 138
 139        try
 3140        {
 141            // Extract methods
 3142            members.AddRange(_typeAnalyzer.AnalyzeMethods(type));
 143
 144            // Extract properties
 3145            members.AddRange(_typeAnalyzer.AnalyzeProperties(type));
 146
 147            // Extract fields
 3148            members.AddRange(_typeAnalyzer.AnalyzeFields(type));
 149
 150            // Extract events
 3151            members.AddRange(_typeAnalyzer.AnalyzeEvents(type));
 152
 153            // Extract constructors
 3154            members.AddRange(_typeAnalyzer.AnalyzeConstructors(type));
 155
 3156            return members;
 157        }
 0158        catch (Exception ex)
 0159        {
 0160            _logger.LogError(
 0161                ex,
 0162                "Error extracting members from type {TypeName}",
 0163                type.FullName);
 0164            return members;
 165        }
 3166    }
 167
 168    /// <summary>
 169    /// Gets all public types from the specified assembly
 170    /// </summary>
 171    /// <param name="assembly">Assembly to get types from</param>
 172    /// <param name="filterConfig">Optional filter configuration to apply</param>
 173    /// <returns>Collection of public types</returns>
 174    public virtual IEnumerable<Type> GetPublicTypes(
 175        Assembly assembly,
 176        Models.Configuration.FilterConfiguration? filterConfig = null)
 2177    {
 2178        if (assembly == null)
 1179        {
 1180            throw new ArgumentNullException(nameof(assembly));
 181        }
 182
 183        try
 1184        {
 185            // Get all exported (public) types from the assembly
 1186            var types = assembly.GetExportedTypes()
 46187                .Where(t => !t.IsCompilerGenerated() && !t.IsSpecialName);
 188
 189            // Apply filtering if configuration is provided
 1190            if (filterConfig != null)
 0191            {
 192                // Filter by namespace includes if specified
 0193                if (filterConfig.IncludeNamespaces.Count > 0)
 0194                {
 0195                    _logger.LogDebug(
 0196                        "Filtering types to include only namespaces: {Namespaces}",
 0197                        string.Join(", ", filterConfig.IncludeNamespaces));
 198
 0199                    types = types.Where(
 0200                        t =>
 0201                            filterConfig.IncludeNamespaces.Any(
 0202                                ns =>
 0203                                    (t.Namespace ?? string.Empty).StartsWith(ns, StringComparison.OrdinalIgnoreCase)));
 0204                }
 205
 206                // Filter by namespace excludes
 0207                if (filterConfig.ExcludeNamespaces.Count > 0)
 0208                {
 0209                    _logger.LogDebug(
 0210                        "Filtering types to exclude namespaces: {Namespaces}",
 0211                        string.Join(", ", filterConfig.ExcludeNamespaces));
 212
 0213                    types = types.Where(
 0214                        t =>
 0215                            !filterConfig.ExcludeNamespaces.Any(
 0216                                ns =>
 0217                                    (t.Namespace ?? string.Empty).StartsWith(ns, StringComparison.OrdinalIgnoreCase)));
 0218                }
 219
 220                // Filter by type name includes if specified
 0221                if (filterConfig.IncludeTypes.Count > 0)
 0222                {
 0223                    _logger.LogDebug(
 0224                        "Filtering types to include only types matching patterns: {Patterns}",
 0225                        string.Join(", ", filterConfig.IncludeTypes));
 226
 0227                    types = types.Where(
 0228                        t =>
 0229                            filterConfig.IncludeTypes.Any(
 0230                                pattern =>
 0231                                    IsTypeMatchingPattern(t, pattern)));
 0232                }
 233
 234                // Filter by type name excludes
 0235                if (filterConfig.ExcludeTypes.Count > 0)
 0236                {
 0237                    _logger.LogDebug(
 0238                        "Filtering types to exclude types matching patterns: {Patterns}",
 0239                        string.Join(", ", filterConfig.ExcludeTypes));
 240
 0241                    types = types.Where(
 0242                        t =>
 0243                            !filterConfig.ExcludeTypes.Any(
 0244                                pattern =>
 0245                                    IsTypeMatchingPattern(t, pattern)));
 0246                }
 247
 248                // Filter internal types if not included
 0249                if (!filterConfig.IncludeInternals)
 0250                {
 0251                    types = types.Where(t => t.IsPublic);
 0252                }
 253
 254                // Filter compiler-generated types if not included
 0255                if (!filterConfig.IncludeCompilerGenerated)
 0256                {
 0257                    types = types.Where(t => !IsCompilerGeneratedType(t));
 0258                }
 0259            }
 260
 46261            return types.OrderBy(t => t.FullName);
 262        }
 0263        catch (ReflectionTypeLoadException ex)
 0264        {
 0265            _logger.LogError(
 0266                ex,
 0267                "Error loading types from assembly {AssemblyName}",
 0268                assembly.GetName().Name);
 269
 270            // Return any types that were successfully loaded
 0271            return ex.Types.Where(t => t != null).Cast<Type>();
 272        }
 0273        catch (Exception ex)
 0274        {
 0275            _logger.LogError(
 0276                ex,
 0277                "Error getting public types from assembly {AssemblyName}",
 0278                assembly.GetName().Name);
 0279            return Enumerable.Empty<Type>();
 280        }
 1281    }
 282
 283    /// <summary>
 284    /// Checks if a type matches a wildcard pattern
 285    /// </summary>
 286    /// <param name="type">Type to check</param>
 287    /// <param name="pattern">Pattern to match against</param>
 288    /// <returns>True if the type matches the pattern, false otherwise</returns>
 289    private bool IsTypeMatchingPattern(Type type, string pattern)
 0290    {
 0291        var typeName = type.FullName ?? type.Name;
 292
 293        // Convert wildcard pattern to regex
 0294        var regexPattern = "^" + System.Text.RegularExpressions.Regex.Escape(pattern)
 0295            .Replace("\\*", ".*")
 0296            .Replace("\\?", ".") + "$";
 297
 298        try
 0299        {
 0300            return System.Text.RegularExpressions.Regex.IsMatch(
 0301                typeName,
 0302                regexPattern,
 0303                System.Text.RegularExpressions.RegexOptions.IgnoreCase);
 304        }
 0305        catch
 0306        {
 307            // If regex fails, fall back to simple string comparison
 0308            return typeName.Equals(pattern, StringComparison.OrdinalIgnoreCase);
 309        }
 0310    }
 311
 312    /// <summary>
 313    /// Checks if a type is compiler-generated
 314    /// </summary>
 315    /// <param name="type">Type to check</param>
 316    /// <returns>True if compiler-generated, false otherwise</returns>
 317    private bool IsCompilerGeneratedType(Type type)
 0318    {
 319        // Check for compiler-generated attributes
 0320        if (type.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true))
 0321        {
 0322            return true;
 323        }
 324
 325        // Check for compiler-generated naming patterns
 0326        return type.Name.Contains('<') ||
 0327               type.Name.StartsWith("__") ||
 0328               type.Name.Contains("AnonymousType") ||
 0329               type.Name.Contains("DisplayClass");
 0330    }
 331}
 332
 333/// <summary>
 334/// Extension methods for reflection types
 335/// </summary>
 336public static class ReflectionExtensions
 337{
 338    /// <summary>
 339    /// Checks if a type is compiler-generated
 340    /// </summary>
 341    /// <param name="type">Type to check</param>
 342    /// <returns>True if compiler-generated, false otherwise</returns>
 343    public static bool IsCompilerGenerated(this Type type)
 344    {
 345        // Check for compiler-generated types like closures, iterators, etc.
 346        if (type.Name.Contains('<') || type.Name.StartsWith("__"))
 347        {
 348            return true;
 349        }
 350
 351        // Check for CompilerGeneratedAttribute using IsDefined for better performance
 352        return type.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true);
 353    }
 354}