using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Speckle.ProxyGenerator.Enums; using Speckle.ProxyGenerator.Extensions; using Speckle.ProxyGenerator.Models; using Speckle.ProxyGenerator.Types; using Speckle.ProxyGenerator.Utils; namespace Speckle.ProxyGenerator.FileGenerators; internal class PartialInterfacesGenerator : BaseGenerator, IFilesGenerator { private List _implementedInterfaces = new(); public PartialInterfacesGenerator(Context context, bool supportsNullable) : base(context, supportsNullable) { } public IEnumerable GenerateFiles() { foreach (var ci in Context.Candidates) { if (TryGenerateFile(ci.Key, ci.Value, out var file)) { yield return file; } } } private bool TryGenerateFile( InterfaceDeclarationSyntax ci, ProxyData pd, [NotNullWhen(true)] out FileData? fileData ) { fileData = default; if ( !TryGetNamedTypeSymbolByFullName( [TypeKind.Interface], ci.Identifier.ToString(), pd.Usings, out var sourceInterfaceSymbol ) ) { return false; } if ( !TryGetNamedTypeSymbolByFullName( [TypeKind.Class, TypeKind.Struct], pd.FullMetadataTypeName, pd.Usings, out var targetClassSymbol ) ) { return false; } var interfaceName = ResolveInterfaceNameWithOptionalTypeConstraints( targetClassSymbol.Symbol, pd.ShortInterfaceName ); fileData = new FileData( $"{sourceInterfaceSymbol.Symbol.GetFullMetadataName()}.g.cs", CreatePartialInterfaceCode( pd.Namespace, targetClassSymbol, sourceInterfaceSymbol, interfaceName, pd ) ); return true; } private string CreatePartialInterfaceCode( string ns, ClassSymbol classSymbol, ClassSymbol interfaceSymbol, string interfaceName, ProxyData proxyData ) { _implementedInterfaces.Clear(); _implementedInterfaces.AddRange( classSymbol.Symbol.ResolveImplementedInterfaces( proxyData.Options.HasFlag(ImplementationOptions.ProxyBaseClasses), proxyData.Options.HasFlag(ImplementationOptions.ProxyInterfaces) ) ); if (proxyData.Options.HasFlag(ImplementationOptions.UseExtendedInterfaces)) { var bases = interfaceSymbol .Symbol.ResolveBaseInterfaces(_implementedInterfaces) .ToList(); if ( bases.Count == 1 && proxyData.Options.HasFlag(ImplementationOptions.ProxyForBaseInterface) ) { proxyData.FullQualifiedMappedTypeName = globalPrefix + bases.Single().GetFullMetadataName(); } _implementedInterfaces.AddRange(bases); //don't readd self if (_implementedInterfaces.Contains(interfaceSymbol.Symbol)) { _implementedInterfaces.Remove(interfaceSymbol.Symbol); } } _implementedInterfaces = _implementedInterfaces.Distinct().ToList(); var isNew = GetExtendsProxyData( classSymbol, proxyData.Options.HasFlag(ImplementationOptions.UseExtendedInterfaces) ) .Any(); var implementedInterfacesNames = _implementedInterfaces .Select(i => i.ToFullyQualifiedDisplayString()) .ToArray(); var implements = implementedInterfacesNames.Any() ? $" : {string.Join(", ", implementedInterfacesNames)}" : string.Empty; var @new = isNew ? "new " : string.Empty; var (namespaceStart, namespaceEnd) = NamespaceBuilder.Build(ns); var events = GenerateEvents(classSymbol, proxyData); var properties = GenerateProperties(classSymbol, proxyData); var methods = GenerateMethods(classSymbol, proxyData).TrimEnd(); return $@"//---------------------------------------------------------------------------------------- // // This code was generated by https://github.com/specklesystems/ProxyGenerator. // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //---------------------------------------------------------------------------------------- {SupportsNullable.IIf("#nullable enable")} using System; {namespaceStart} public partial interface {interfaceName}{implements} {{ {@new}{classSymbol} _Instance {{ get; }} {events + properties + methods} }} {namespaceEnd} {SupportsNullable.IIf("#nullable restore")}"; } private Func InterfaceFilter() where T : ISymbol { var hashSet = new HashSet(); foreach (var @interface in _implementedInterfaces) { var members = @interface.AllInterfaces.Aggregate( @interface.GetMembers(), (xs, x) => xs.AddRange(x.GetMembers()) ); foreach (var member in members) { hashSet.Add(member.Name); } } // Member is not already implemented in another interface. return t => !hashSet.Contains(t.Name); } private string GenerateProperties(ClassSymbol targetClassSymbol, ProxyData proxyData) { var str = new StringBuilder(); foreach ( var property in MemberHelper.GetPublicProperties( targetClassSymbol, proxyData, InterfaceFilter() ) ) { var (_, type) = GetPropertyType(property, out var isReplaced); var getterSetter = isReplaced ? ToPropertyDetails(property, type) : ToPropertyDetails(property); if (getterSetter is null) { continue; } var propertyName = getterSetter.Value.PropertyName; if (property.IsIndexer) { var methodParameters = GetMethodParameters(property.Parameters, true); propertyName = $"this[{string.Join(", ", methodParameters)}]"; } foreach (var attribute in property.GetAttributesAsList()) { str.AppendLine($" {attribute}"); } str.AppendLine( $" {getterSetter.Value.PropertyType} {propertyName} {getterSetter.Value.GetSet}" ); str.AppendLine(); } return str.ToString(); } private string GenerateMethods(ClassSymbol targetClassSymbol, ProxyData proxyData) { var str = new StringBuilder(); foreach ( var method in MemberHelper.GetPublicMethods( targetClassSymbol, proxyData, InterfaceFilter() ) ) { var methodParameters = GetMethodParameters(method.Parameters, true); var whereStatement = GetWhereStatementFromMethod(method); foreach (var attribute in method.GetAttributesAsList()) { str.AppendLine($" {attribute}"); } var (_, type) = GetReplacedTypeAsString(method.ReturnType, null, out _); str.AppendLine( $" {type} {method.GetMethodNameWithOptionalTypeParameters()}({string.Join(", ", methodParameters)}){whereStatement};" ); str.AppendLine(); } return str.ToString(); } private string GenerateEvents(ClassSymbol targetClassSymbol, ProxyData proxyData) { var str = new StringBuilder(); foreach ( var @event in MemberHelper.GetPublicEvents( targetClassSymbol, proxyData, InterfaceFilter() ) ) { var ps = @event.First().Parameters.First(); string? type; if (ps.GetTypeEnum() == TypeEnum.Complex) { (_, type) = GetParameterType(ps, out _); } else { type = ps.Type.ToString(); } foreach (var attribute in ps.GetAttributesAsList()) { str.AppendLine($" {attribute}"); } str.AppendLine($" event {type} {@event.Key.GetSanitizedName()};"); str.AppendLine(); } return str.ToString(); } public (string PropertyType, string? PropertyName, string GetSet)? ToPropertyDetails( IPropertySymbol property, string? overrideType = null ) { var getIsPublic = property.GetMethod.IsPublic(); var setIsPublic = property.SetMethod.IsPublic(); if (!getIsPublic && !setIsPublic) { return null; } var get = getIsPublic ? "get; " : string.Empty; var set = setIsPublic ? "set; " : string.Empty; string? type; if (!string.IsNullOrEmpty(overrideType)) { type = overrideType; } else { (_, type) = FixType( property.Type.ToFullyQualifiedDisplayString(), property.NullableAnnotation, null ); } return (type!, property.GetSanitizedName(), $"{{ {get}{set}}}"); } }