Skip to content
Open
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 @@ -39,13 +40,19 @@ ImmutableArray<ITypeSymbol> enumerations
return;
}

var interfaceNames = 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, interfaceNames);

var hintName = $"{typeNamespace}.I{type.Name}";
context.AddSource(hintName, code);
Expand Down
120 changes: 84 additions & 36 deletions AutomaticInterface/AutomaticInterface/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,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 generationAttribute = GetGenerationAttribute(typeSymbol);
var asInternal = GetAsInternal(generationAttribute);
var symbolDetails = GetSymbolDetails(typeSymbol, classSyntax);
Expand All @@ -62,7 +83,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 @@ -71,15 +94,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 AttributeData? GetGenerationAttribute(ISymbol typeSymbol)
{
return typeSymbol
Expand All @@ -90,20 +130,6 @@ is not ClassDeclarationSyntax classSyntax
);
}

private static string GetNameSpace(ISymbol typeSymbol, AttributeData? generationAttribute)
{
if (generationAttribute == null)
{
return typeSymbol.ContainingNamespace.ToDisplayString();
}

var customNs = generationAttribute.ConstructorArguments.FirstOrDefault().Value?.ToString();

return string.IsNullOrWhiteSpace(customNs)
? typeSymbol.ContainingNamespace.ToDisplayString()
: customNs!;
}

private static bool GetAsInternal(AttributeData? generationAttribute)
{
if (generationAttribute == null)
Expand Down Expand Up @@ -132,7 +158,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 @@ -143,10 +173,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 @@ -155,22 +189,24 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth

var paramResult = new HashSet<string>();
method
.Parameters.Select(x => x.ToDisplayString(FullyQualifiedDisplayFormat))
.Parameters.Select(x =>
x.ToDisplayString(FullyQualifiedDisplayFormat, 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 @@ -226,7 +262,11 @@ private static bool IsNullable(ITypeSymbol typeSymbol)
return false;
}

private static void AddEventsToInterface(List<ISymbol> members, InterfaceBuilder codeGenerator)
private static void AddEventsToInterface(
List<ISymbol> members,
InterfaceBuilder codeGenerator,
List<string> generatedInterfaceNames
)
{
members
.Where(x => x.Kind == SymbolKind.Event)
Expand All @@ -243,15 +283,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 @@ -274,7 +315,7 @@ InterfaceBuilder interfaceGenerator

interfaceGenerator.AddPropertyToInterface(
name,
type.ToDisplayString(FullyQualifiedDisplayFormat),
type.ToDisplayString(FullyQualifiedDisplayFormat, generatedInterfaceNames),
hasGet,
hasSet,
isRef,
Expand Down Expand Up @@ -332,11 +373,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
4 changes: 3 additions & 1 deletion AutomaticInterface/AutomaticInterface/InterfaceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ public string Build()
cb.AppendLine(
"[global::System.CodeDom.Compiler.GeneratedCode(\"AutomaticInterface\", \"\")]"
);
cb.AppendLine($"{(asInternal ? "internal" : "public")} partial interface {interfaceName}{genericType}");
cb.AppendLine(
$"{(asInternal ? "internal" : "public")} partial interface {interfaceName}{genericType}"
);
cb.AppendLine("{");

cb.Indent();
Expand Down
Loading
Loading