Files
ProxyGenerator/src/Speckle.ProxyGenerator/FileGenerators/PartialInterfacesGenerator.cs
T
Adam Hathcock c71fc31132 Rhino required fixes (#9)
* add fixes for structs

* handle null non-nullables to nullable nulls

* test fix

* add enum test even if it's gross looking

* generate nullable adapt when needed

* fix adapter tests

* don't generate static events

* fmt

* mark for release
2024-06-18 13:04:08 +01:00

322 lines
9.8 KiB
C#

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<INamedTypeSymbol> _implementedInterfaces = new();
public PartialInterfacesGenerator(Context context, bool supportsNullable)
: base(context, supportsNullable) { }
public IEnumerable<FileData> 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 $@"//----------------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//----------------------------------------------------------------------------------------
{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<T, bool> InterfaceFilter<T>()
where T : ISymbol
{
var hashSet = new HashSet<string>();
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<IPropertySymbol>()
)
)
{
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<IMethodSymbol>()
)
)
{
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<IMethodSymbol>()
)
)
{
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}}}");
}
}