using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace QuestPDF.InteropGenerators;
///
/// Analyzes the QuestPDF public API and extracts methods suitable for interop
///
public static class PublicApiAnalyzer
{
///
/// Collects all public extension methods from the assembly
///
public static List CollectExtensionMethods(INamespaceSymbol rootNamespace)
{
var methods = new List();
CollectExtensionMethodsRecursive(rootNamespace, methods);
return methods;
}
///
/// Collects all public methods from Fluent API classes (descriptors, configurations, handlers, etc.)
///
public static List CollectFluentApiMethods(INamespaceSymbol rootNamespace)
{
var methods = new List();
CollectFluentApiMethodsRecursive(rootNamespace, methods);
return methods;
}
///
/// Collects all interop-eligible methods: extension methods + fluent API class methods
///
public static List CollectAllInteropMethods(INamespaceSymbol rootNamespace)
{
var methods = new List();
CollectExtensionMethodsRecursive(rootNamespace, methods);
CollectFluentApiMethodsRecursive(rootNamespace, methods);
return methods;
}
private static void CollectExtensionMethodsRecursive(INamespaceSymbol ns, List methods)
{
foreach (var member in ns.GetMembers())
{
if (member is INamespaceSymbol childNs)
{
CollectExtensionMethodsRecursive(childNs, methods);
}
else if (member is INamedTypeSymbol type)
{
CollectFromType(type, methods);
}
}
}
private static void CollectFromType(INamedTypeSymbol type, List methods)
{
if (type.DeclaredAccessibility != Accessibility.Public || type.IsImplicitlyDeclared)
return;
foreach (var member in type.GetMembers())
{
if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary, IsExtensionMethod: true, DeclaredAccessibility: Accessibility.Public } method
&& !method.IsImplicitlyDeclared)
{
methods.Add(method);
}
}
foreach (var nestedType in type.GetTypeMembers())
{
CollectFromType(nestedType, methods);
}
}
private static void CollectFluentApiMethodsRecursive(INamespaceSymbol ns, List methods)
{
foreach (var member in ns.GetMembers())
{
if (member is INamespaceSymbol childNs)
{
CollectFluentApiMethodsRecursive(childNs, methods);
}
else if (member is INamedTypeSymbol type)
{
CollectFluentApiMethodsFromType(type, methods);
}
}
}
private static void CollectFluentApiMethodsFromType(INamedTypeSymbol type, List methods)
{
if (type.DeclaredAccessibility != Accessibility.Public || type.IsImplicitlyDeclared)
return;
// Check if this is a Fluent API class (descriptors, configurations, handlers, etc.)
if (!IsFluentApiClass(type))
return;
foreach (var member in type.GetMembers())
{
// Collect public instance methods (not extension methods, not constructors, not property accessors)
if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary, IsExtensionMethod: false, DeclaredAccessibility: Accessibility.Public, IsStatic: false } method
&& !method.IsImplicitlyDeclared)
{
methods.Add(method);
}
}
// Process nested types
foreach (var nestedType in type.GetTypeMembers())
{
CollectFluentApiMethodsFromType(nestedType, methods);
}
}
///
/// Determines if a type is part of the Fluent API (descriptors, configurations, handlers, etc.)
///
private static bool IsFluentApiClass(INamedTypeSymbol type)
{
var typeName = type.Name;
var namespaceName = type.ContainingNamespace?.ToDisplayString() ?? "";
// Check if it's in the Fluent namespace
if (namespaceName.Contains("QuestPDF.Fluent"))
return true;
// Check for common Fluent API naming patterns
if (typeName.EndsWith("Descriptor") ||
typeName.EndsWith("Configuration") ||
typeName.EndsWith("Handler") ||
typeName.EndsWith("Builder") ||
typeName.EndsWith("Settings"))
return true;
// Check if the type implements IContainer or IDocumentContainer
foreach (var iface in type.AllInterfaces)
{
var ifaceName = iface.ToDisplayString();
if (ifaceName.Contains("IContainer") || ifaceName.Contains("IDocumentContainer"))
return true;
}
return false;
}
///
/// Checks if a method is supported for interop generation
///
public static bool IsSupported(IMethodSymbol method)
{
// Async methods are not supported
if (method.IsAsync)
return false;
// Check return type
if (!IsSupportedType(method.ReturnType))
return false;
// Exclude methods that return Task or Task
if (IsTaskRelatedType(method.ReturnType))
return false;
// Check all parameter types
foreach (var parameter in method.Parameters)
{
if (!IsSupportedType(parameter.Type))
return false;
// Exclude methods with Task-related parameters
if (IsTaskRelatedType(parameter.Type))
return false;
// Ref/out parameters are not supported in UnmanagedCallersOnly
if (parameter.RefKind != RefKind.None)
return false;
}
// Generic methods are not supported
if (method.IsGenericMethod)
return false;
return true;
}
///
/// Checks if a type is supported for interop
///
public static bool IsSupportedType(ITypeSymbol type)
{
// Void is supported
if (type.SpecialType == SpecialType.System_Void)
return true;
// Blittable primitive types
if (type.SpecialType is
SpecialType.System_Boolean or
SpecialType.System_Byte or
SpecialType.System_SByte or
SpecialType.System_Int16 or
SpecialType.System_UInt16 or
SpecialType.System_Int32 or
SpecialType.System_UInt32 or
SpecialType.System_Int64 or
SpecialType.System_UInt64 or
SpecialType.System_Single or
SpecialType.System_Double or
SpecialType.System_IntPtr or
SpecialType.System_UIntPtr)
return true;
// Pointers are supported
if (type.TypeKind == TypeKind.Pointer)
return true;
// Function pointers are supported
if (type.TypeKind == TypeKind.FunctionPointer)
return true;
// Value types (structs) that are blittable might be supported
// For simplicity, we'll allow struct types but developers should ensure they're blittable
if (type.TypeKind == TypeKind.Struct && !type.IsRefLikeType)
return true;
// Enums are supported (backed by primitive types)
if (type.TypeKind == TypeKind.Enum)
return true;
// Classes and interfaces are supported via handle boxing (passed as nint)
if (type.TypeKind is TypeKind.Class or TypeKind.Interface)
return true;
// Generic types, arrays, delegates are not supported
if (type is INamedTypeSymbol { IsGenericType: true })
return false;
if (type.TypeKind == TypeKind.Delegate)
return false;
if (type.TypeKind == TypeKind.Array)
return false;
return false;
}
///
/// Checks if a type is a reference type (class or interface)
///
public static bool IsReferenceType(ITypeSymbol type)
{
return type.TypeKind is TypeKind.Class or TypeKind.Interface;
}
///
/// Checks if a type is Task-related (Task, CancellationToken, etc.)
///
public static bool IsTaskRelatedType(ITypeSymbol type)
{
var fullName = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
// Check for Task, Task, ValueTask, ValueTask
if (fullName.StartsWith("global::System.Threading.Tasks.Task") ||
fullName.StartsWith("global::System.Threading.Tasks.ValueTask"))
return true;
// Check for CancellationToken
if (fullName == "global::System.Threading.CancellationToken")
return true;
// Check for IAsyncEnumerable and IAsyncEnumerator
if (fullName.StartsWith("global::System.Collections.Generic.IAsyncEnumerable") ||
fullName.StartsWith("global::System.Collections.Generic.IAsyncEnumerator"))
return true;
return false;
}
}