< Summary

Information
Class: DotNetApiDiff.ApiExtraction.ReflectionExtensions
Assembly: DotNetApiDiff
File(s): /home/runner/work/dotnet-api-diff/dotnet-api-diff/src/DotNetApiDiff/ApiExtraction/ApiExtractor.cs
Line coverage
66%
Covered lines: 4
Uncovered lines: 2
Coverable lines: 6
Total lines: 354
Line coverage: 66.6%
Branch coverage
50%
Covered branches: 2
Total branches: 4
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsCompilerGenerated(...)50%5466.66%

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>
 23    public ApiExtractor(ITypeAnalyzer typeAnalyzer, ILogger<ApiExtractor> logger)
 24    {
 25        _typeAnalyzer = typeAnalyzer ?? throw new ArgumentNullException(nameof(typeAnalyzer));
 26        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
 27    }
 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)
 38    {
 39        if (assembly == null)
 40        {
 41            throw new ArgumentNullException(nameof(assembly));
 42        }
 43
 44        _logger.LogInformation("Extracting API members from assembly: {AssemblyName}", assembly.GetName().Name);
 45
 46        if (filterConfig != null)
 47        {
 48            _logger.LogInformation(
 49                "Applying filter configuration with {IncludeCount} includes and {ExcludeCount} excludes",
 50                filterConfig.IncludeNamespaces.Count + filterConfig.IncludeTypes.Count,
 51                filterConfig.ExcludeNamespaces.Count + filterConfig.ExcludeTypes.Count);
 52        }
 53
 54        var apiMembers = new List<ApiMember>();
 55
 56        try
 57        {
 58            // Get all public types from the assembly, applying filters if provided
 59            var types = GetPublicTypes(assembly, filterConfig).ToList();
 60            _logger.LogDebug(
 61                "Found {TypeCount} public types in assembly {AssemblyName} after filtering",
 62                types.Count,
 63                assembly.GetName().Name);
 64
 65            // Process each type
 66            foreach (var type in types)
 67            {
 68                try
 69                {
 70                    // Add the type itself
 71                    var typeMember = _typeAnalyzer.AnalyzeType(type);
 72                    apiMembers.Add(typeMember);
 73
 74                    // Add all members of the type
 75                    var typeMembers = ExtractTypeMembers(type).ToList();
 76                    apiMembers.AddRange(typeMembers);
 77
 78                    _logger.LogDebug(
 79                        "Extracted {MemberCount} members from type {TypeName}",
 80                        typeMembers.Count,
 81                        type.FullName);
 82                }
 83                catch (Exception ex)
 84                {
 85                    _logger.LogError(ex, "Error extracting members from type {TypeName}", type.FullName);
 86                }
 87            }
 88
 89            _logger.LogInformation(
 90                "Extracted {MemberCount} total API members from assembly {AssemblyName}",
 91                apiMembers.Count,
 92                assembly.GetName().Name);
 93
 94            return apiMembers;
 95        }
 96        catch (ReflectionTypeLoadException ex)
 97        {
 98            _logger.LogError(ex, "Error loading types from assembly {AssemblyName}", assembly.GetName().Name);
 99
 100            // Log the loader exceptions for more detailed diagnostics
 101            if (ex.LoaderExceptions != null)
 102            {
 103                foreach (var loaderEx in ex.LoaderExceptions)
 104                {
 105                    if (loaderEx != null)
 106                    {
 107                        _logger.LogError(loaderEx, "Loader exception: {Message}", loaderEx.Message);
 108                    }
 109                }
 110            }
 111
 112            // Return any types that were successfully loaded
 113            return apiMembers;
 114        }
 115        catch (Exception ex)
 116        {
 117            _logger.LogError(
 118                ex,
 119                "Error extracting API members from assembly {AssemblyName}",
 120                assembly.GetName().Name);
 121            return apiMembers;
 122        }
 123    }
 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)
 131    {
 132        if (type == null)
 133        {
 134            throw new ArgumentNullException(nameof(type));
 135        }
 136
 137        var members = new List<ApiMember>();
 138
 139        try
 140        {
 141            // Extract methods
 142            members.AddRange(_typeAnalyzer.AnalyzeMethods(type));
 143
 144            // Extract properties
 145            members.AddRange(_typeAnalyzer.AnalyzeProperties(type));
 146
 147            // Extract fields
 148            members.AddRange(_typeAnalyzer.AnalyzeFields(type));
 149
 150            // Extract events
 151            members.AddRange(_typeAnalyzer.AnalyzeEvents(type));
 152
 153            // Extract constructors
 154            members.AddRange(_typeAnalyzer.AnalyzeConstructors(type));
 155
 156            return members;
 157        }
 158        catch (Exception ex)
 159        {
 160            _logger.LogError(
 161                ex,
 162                "Error extracting members from type {TypeName}",
 163                type.FullName);
 164            return members;
 165        }
 166    }
 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)
 177    {
 178        if (assembly == null)
 179        {
 180            throw new ArgumentNullException(nameof(assembly));
 181        }
 182
 183        try
 184        {
 185            // Get all exported (public) types from the assembly
 186            var types = assembly.GetExportedTypes()
 187                .Where(t => !t.IsCompilerGenerated() && !t.IsSpecialName);
 188
 189            // Apply filtering if configuration is provided
 190            if (filterConfig != null)
 191            {
 192                // Filter by namespace includes if specified
 193                if (filterConfig.IncludeNamespaces.Count > 0)
 194                {
 195                    _logger.LogDebug(
 196                        "Filtering types to include only namespaces: {Namespaces}",
 197                        string.Join(", ", filterConfig.IncludeNamespaces));
 198
 199                    types = types.Where(
 200                        t =>
 201                            filterConfig.IncludeNamespaces.Any(
 202                                ns =>
 203                                    (t.Namespace ?? string.Empty).StartsWith(ns, StringComparison.OrdinalIgnoreCase)));
 204                }
 205
 206                // Filter by namespace excludes
 207                if (filterConfig.ExcludeNamespaces.Count > 0)
 208                {
 209                    _logger.LogDebug(
 210                        "Filtering types to exclude namespaces: {Namespaces}",
 211                        string.Join(", ", filterConfig.ExcludeNamespaces));
 212
 213                    types = types.Where(
 214                        t =>
 215                            !filterConfig.ExcludeNamespaces.Any(
 216                                ns =>
 217                                    (t.Namespace ?? string.Empty).StartsWith(ns, StringComparison.OrdinalIgnoreCase)));
 218                }
 219
 220                // Filter by type name includes if specified
 221                if (filterConfig.IncludeTypes.Count > 0)
 222                {
 223                    _logger.LogDebug(
 224                        "Filtering types to include only types matching patterns: {Patterns}",
 225                        string.Join(", ", filterConfig.IncludeTypes));
 226
 227                    types = types.Where(
 228                        t =>
 229                            filterConfig.IncludeTypes.Any(
 230                                pattern =>
 231                                    IsTypeMatchingPattern(t, pattern)));
 232                }
 233
 234                // Filter by type name excludes
 235                if (filterConfig.ExcludeTypes.Count > 0)
 236                {
 237                    _logger.LogDebug(
 238                        "Filtering types to exclude types matching patterns: {Patterns}",
 239                        string.Join(", ", filterConfig.ExcludeTypes));
 240
 241                    types = types.Where(
 242                        t =>
 243                            !filterConfig.ExcludeTypes.Any(
 244                                pattern =>
 245                                    IsTypeMatchingPattern(t, pattern)));
 246                }
 247
 248                // Filter internal types if not included
 249                if (!filterConfig.IncludeInternals)
 250                {
 251                    types = types.Where(t => t.IsPublic);
 252                }
 253
 254                // Filter compiler-generated types if not included
 255                if (!filterConfig.IncludeCompilerGenerated)
 256                {
 257                    types = types.Where(t => !IsCompilerGeneratedType(t));
 258                }
 259            }
 260
 261            return types.OrderBy(t => t.FullName);
 262        }
 263        catch (ReflectionTypeLoadException ex)
 264        {
 265            _logger.LogError(
 266                ex,
 267                "Error loading types from assembly {AssemblyName}",
 268                assembly.GetName().Name);
 269
 270            // Return any types that were successfully loaded
 271            return ex.Types.Where(t => t != null).Cast<Type>();
 272        }
 273        catch (Exception ex)
 274        {
 275            _logger.LogError(
 276                ex,
 277                "Error getting public types from assembly {AssemblyName}",
 278                assembly.GetName().Name);
 279            return Enumerable.Empty<Type>();
 280        }
 281    }
 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)
 290    {
 291        var typeName = type.FullName ?? type.Name;
 292
 293        // Convert wildcard pattern to regex
 294        var regexPattern = "^" + System.Text.RegularExpressions.Regex.Escape(pattern)
 295            .Replace("\\*", ".*")
 296            .Replace("\\?", ".") + "$";
 297
 298        try
 299        {
 300            return System.Text.RegularExpressions.Regex.IsMatch(
 301                typeName,
 302                regexPattern,
 303                System.Text.RegularExpressions.RegexOptions.IgnoreCase);
 304        }
 305        catch
 306        {
 307            // If regex fails, fall back to simple string comparison
 308            return typeName.Equals(pattern, StringComparison.OrdinalIgnoreCase);
 309        }
 310    }
 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)
 318    {
 319        // Check for compiler-generated attributes
 320        if (type.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true))
 321        {
 322            return true;
 323        }
 324
 325        // Check for compiler-generated naming patterns
 326        return type.Name.Contains('<') ||
 327               type.Name.StartsWith("__") ||
 328               type.Name.Contains("AnonymousType") ||
 329               type.Name.Contains("DisplayClass");
 330    }
 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)
 45344    {
 345        // Check for compiler-generated types like closures, iterators, etc.
 45346        if (type.Name.Contains('<') || type.Name.StartsWith("__"))
 0347        {
 0348            return true;
 349        }
 350
 351        // Check for CompilerGeneratedAttribute using IsDefined for better performance
 45352        return type.IsDefined(typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute), true);
 45353    }
 354}