Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

Expand Down Expand Up @@ -40,13 +41,19 @@ ImmutableArray<ITypeSymbol> enumerations
return;
}

var generatedInterfaceNames = enumerations
.Select(Builder.GetInterfaceNameFor)
.Where(name => name != null)
.Cast<string>()
.ToList();

foreach (var type in enumerations)
{
var typeNamespace = type.ContainingNamespace.IsGlobalNamespace
? $"${Guid.NewGuid()}"
: $"{type.ContainingNamespace}";

var code = Builder.BuildInterfaceFor(type);
var code = Builder.BuildInterfaceFor(type, generatedInterfaceNames);

var hintName = $"{typeNamespace}.I{type.Name}";
context.AddSource(hintName, code);
Expand Down
130 changes: 86 additions & 44 deletions AutomaticInterface/DotnetAutomaticInterface/Builder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -41,17 +40,37 @@ private static string InheritDoc(ISymbol source) =>
miscellaneousOptions: FullyQualifiedDisplayFormat.MiscellaneousOptions
);

public static string BuildInterfaceFor(ITypeSymbol typeSymbol)
public static string? GetInterfaceNameFor(ITypeSymbol typeSymbol)
{
if (
typeSymbol.DeclaringSyntaxReferences.First().GetSyntax()
is not ClassDeclarationSyntax classSyntax
|| typeSymbol is not INamedTypeSymbol namedTypeSymbol
)
var declarationAndNamedTypeSymbol = GetClassDeclarationMetadata(typeSymbol);
if (declarationAndNamedTypeSymbol == null)
{
return null;
}

var (classSyntax, _) = declarationAndNamedTypeSymbol.Value;

var symbolDetails = GetSymbolDetails(typeSymbol, classSyntax);

return $"global::{symbolDetails.NamespaceName}.{symbolDetails.InterfaceName}";
}

/// <param name="typeSymbol">The symbol from which the interface will be built</param>
/// <param name="generatedInterfaceNames">A list of interface names that will be generated in this session. Used to resolve type references to interfaces that haven't yet been generated</param>
/// <returns></returns>
public static string BuildInterfaceFor(
ITypeSymbol typeSymbol,
List<string> generatedInterfaceNames
)
{
var declarationAndNamedTypeSymbol = GetClassDeclarationMetadata(typeSymbol);
if (declarationAndNamedTypeSymbol == null)
{
return string.Empty;
}

var (classSyntax, namedTypeSymbol) = declarationAndNamedTypeSymbol.Value;

var symbolDetails = GetSymbolDetails(typeSymbol, classSyntax);
var interfaceGenerator = new InterfaceBuilder(
symbolDetails.NamespaceName,
Expand All @@ -60,7 +79,9 @@ is not ClassDeclarationSyntax classSyntax
);

interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax));
interfaceGenerator.AddGeneric(GetGeneric(classSyntax, namedTypeSymbol));
interfaceGenerator.AddGeneric(
GetGeneric(classSyntax, namedTypeSymbol, generatedInterfaceNames)
);

var members = typeSymbol
.GetAllMembers()
Expand All @@ -69,15 +90,32 @@ is not ClassDeclarationSyntax classSyntax
.Where(x => !HasIgnoreAttribute(x))
.ToList();

AddPropertiesToInterface(members, interfaceGenerator);
AddMethodsToInterface(members, interfaceGenerator);
AddEventsToInterface(members, interfaceGenerator);
AddPropertiesToInterface(members, interfaceGenerator, generatedInterfaceNames);
AddMethodsToInterface(members, interfaceGenerator, generatedInterfaceNames);
AddEventsToInterface(members, interfaceGenerator, generatedInterfaceNames);

var generatedCode = interfaceGenerator.Build();

return generatedCode;
}

private static (
ClassDeclarationSyntax Syntax,
INamedTypeSymbol NamedTypeSymbol
)? GetClassDeclarationMetadata(ITypeSymbol typeSymbol)
{
if (
typeSymbol.DeclaringSyntaxReferences.First().GetSyntax()
is not ClassDeclarationSyntax classSyntax
|| typeSymbol is not INamedTypeSymbol namedTypeSymbol
)
{
return null;
}

return (classSyntax, namedTypeSymbol);
}

private static GeneratedSymbolDetails GetSymbolDetails(
ITypeSymbol typeSymbol,
ClassDeclarationSyntax classSyntax
Expand All @@ -93,7 +131,11 @@ ClassDeclarationSyntax classSyntax
return new GeneratedSymbolDetails(generationAttribute, typeSymbol, classSyntax);
}

private static void AddMethodsToInterface(List<ISymbol> members, InterfaceBuilder codeGenerator)
private static void AddMethodsToInterface(
List<ISymbol> members,
InterfaceBuilder codeGenerator,
List<string> generatedInterfaceNames
)
{
members
.Where(x => x.Kind == SymbolKind.Method)
Expand All @@ -104,10 +146,14 @@ private static void AddMethodsToInterface(List<ISymbol> members, InterfaceBuilde
.GroupBy(x => x.ToDisplayString(FullyQualifiedDisplayFormatForGrouping))
.Select(g => g.First())
.ToList()
.ForEach(method => AddMethod(codeGenerator, method));
.ForEach(method => AddMethod(codeGenerator, method, generatedInterfaceNames));
}

private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol method)
private static void AddMethod(
InterfaceBuilder codeGenerator,
IMethodSymbol method,
List<string> generatedInterfaceNames
)
{
var returnType = method.ReturnType;
var name = method.Name;
Expand All @@ -116,22 +162,28 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth

var paramResult = new HashSet<string>();
method
.Parameters.Select(p => GetParameterDisplayString(p, codeGenerator.HasNullable))
.Parameters.Select(p =>
p.ToDisplayString(
FullyQualifiedDisplayFormat,
codeGenerator.HasNullable,
generatedInterfaceNames
)
)
.ToList()
.ForEach(x => paramResult.Add(x));

var typedArgs = method
.TypeParameters.Select(arg =>
(
arg.ToDisplayString(FullyQualifiedDisplayFormat),
arg.GetWhereStatement(FullyQualifiedDisplayFormat)
arg.GetWhereStatement(FullyQualifiedDisplayFormat, generatedInterfaceNames)
)
)
.ToList();

codeGenerator.AddMethodToInterface(
name,
returnType.ToDisplayString(FullyQualifiedDisplayFormat),
returnType.ToDisplayString(FullyQualifiedDisplayFormat, generatedInterfaceNames),
InheritDoc(method),
paramResult,
typedArgs
Expand Down Expand Up @@ -187,29 +239,11 @@ private static bool IsNullable(ITypeSymbol typeSymbol)
return false;
}

private static string GetParameterDisplayString(
IParameterSymbol param,
bool nullableContextEnabled
private static void AddEventsToInterface(
List<ISymbol> members,
InterfaceBuilder codeGenerator,
List<string> generatedInterfaceNames
)
{
// If this parameter has default value null and we're enabling the nullable context, we need to force the nullable annotation if there isn't one already
if (
param.HasExplicitDefaultValue
&& param.ExplicitDefaultValue is null
&& param.NullableAnnotation != NullableAnnotation.Annotated
&& param.Type.IsReferenceType
&& nullableContextEnabled
)
{
var addNullable = new AddNullableAnnotationSyntaxRewriter();
return addNullable
.Visit(param.DeclaringSyntaxReferences.First().GetSyntax())
.ToFullString();
}
return param.ToDisplayString(FullyQualifiedDisplayFormat);
}

private static void AddEventsToInterface(List<ISymbol> members, InterfaceBuilder codeGenerator)
{
members
.Where(x => x.Kind == SymbolKind.Event)
Expand All @@ -226,15 +260,16 @@ private static void AddEventsToInterface(List<ISymbol> members, InterfaceBuilder

codeGenerator.AddEventToInterface(
name,
type.ToDisplayString(FullyQualifiedDisplayFormat),
type.ToDisplayString(FullyQualifiedDisplayFormat, generatedInterfaceNames),
InheritDoc(evt)
);
});
}

private static void AddPropertiesToInterface(
List<ISymbol> members,
InterfaceBuilder interfaceGenerator
InterfaceBuilder interfaceGenerator,
List<string> generatedInterfaceNames
)
{
members
Expand All @@ -257,7 +292,7 @@ InterfaceBuilder interfaceGenerator

interfaceGenerator.AddPropertyToInterface(
name,
type.ToDisplayString(FullyQualifiedDisplayFormat),
type.ToDisplayString(FullyQualifiedDisplayFormat, generatedInterfaceNames),
hasGet,
hasSet,
isRef,
Expand Down Expand Up @@ -314,11 +349,18 @@ private static string GetDocumentationForClass(CSharpSyntaxNode classSyntax)
return trivia.ToFullString().Trim();
}

private static string GetGeneric(TypeDeclarationSyntax classSyntax, INamedTypeSymbol typeSymbol)
private static string GetGeneric(
TypeDeclarationSyntax classSyntax,
INamedTypeSymbol typeSymbol,
List<string> generatedInterfaceNames
)
{
var whereStatements = typeSymbol
.TypeParameters.Select(typeParameter =>
typeParameter.GetWhereStatement(FullyQualifiedDisplayFormat)
typeParameter.GetWhereStatement(
FullyQualifiedDisplayFormat,
generatedInterfaceNames
)
)
.Where(constraint => !string.IsNullOrEmpty(constraint));

Expand Down
Loading