c71fc31132
* 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
322 lines
9.8 KiB
C#
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}}}");
|
|
}
|
|
}
|