initial code

This commit is contained in:
Stef Heyenrath
2021-07-23 16:28:11 +02:00
parent af0a3e0a5d
commit 7c68455a8e
15 changed files with 527 additions and 0 deletions
@@ -0,0 +1,46 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31512.422
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED3DA9DD-1E07-444B-A2D7-2DBA280F96D4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2CE637DC-E8F5-4603-8B57-E51A32F631F1}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{19009F5B-3267-45E2-A8B6-89F2AB47D72C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src-examples", "src-examples", "{38BA087F-EDA1-4F8A-A140-85B84791B815}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProxyInterfaceSourceGenerator", "src\ProxyInterfaceSourceGenerator\ProxyInterfaceSourceGenerator.csproj", "{C880D4B0-1E9E-4449-AEF8-0A93FB2EDFAD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProxyInterfaceConsumer", "src-examples\ProxyInterfaceConsumer\ProxyInterfaceConsumer.csproj", "{7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C880D4B0-1E9E-4449-AEF8-0A93FB2EDFAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C880D4B0-1E9E-4449-AEF8-0A93FB2EDFAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C880D4B0-1E9E-4449-AEF8-0A93FB2EDFAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C880D4B0-1E9E-4449-AEF8-0A93FB2EDFAD}.Release|Any CPU.Build.0 = Release|Any CPU
{7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E0A10EE-CCC3-4281-9541-B0AF037D3DF9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C880D4B0-1E9E-4449-AEF8-0A93FB2EDFAD} = {ED3DA9DD-1E07-444B-A2D7-2DBA280F96D4}
{7E0A10EE-CCC3-4281-9541-B0AF037D3DF9} = {38BA087F-EDA1-4F8A-A140-85B84791B815}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {585F071D-051D-441C-9C6B-226D9E15A1F5}
EndGlobalSection
EndGlobal
@@ -0,0 +1,7 @@
namespace SourceGeneratorInterface
{
[ProxyInterfaceGenerator.Proxy(typeof(SourceGeneratorInterface.Person))]
public partial interface IPerson
{
}
}
@@ -0,0 +1,33 @@
namespace SourceGeneratorInterface
{
public class Person
{
private int PrivateId { get; }
public int Id { get; }
public long? NullableLong { get; }
public string Name { get; set; }
public Address Address { get; set; }
public E E { get; set; }
public int Add(string s)
{
return 600;
}
}
public class Address
{
public int X { get; }
}
public enum E
{
V1,
V2
}
}
@@ -0,0 +1,11 @@
namespace SourceGeneratorInterface
{
public class Program
{
public static void Main()
{
IPerson ip = null;
//var i = ip.Id;
}
}
}
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.10.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ProxyInterfaceSourceGenerator\ProxyInterfaceSourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
@@ -0,0 +1,27 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Extensions
{
internal static class SymbolExtensions
{
public static string ToCode(this IPropertySymbol property)
{
string get = property.GetMethod != null ? "get; " : string.Empty;
string set = property.SetMethod != null ? "set; " : string.Empty;
return $"{property.Type} {property.Name} {{ {get}{set}}}";
}
public static string ToCode(this IMethodSymbol method)
{
var parameters = new List<string>();
foreach (var ps in method.Parameters)
{
parameters.Add($"{ps.Type} {ps.Name}");
}
return $"{method.ReturnType} {method.Name}({string.Join(", ", parameters)});";
}
}
}
@@ -0,0 +1,9 @@
namespace ProxyInterfaceSourceGenerator.FileGenerators
{
internal record Data
{
public string FileName { get; set; }
public string Text { get; set; }
}
}
@@ -0,0 +1,7 @@
namespace ProxyInterfaceSourceGenerator.FileGenerators
{
internal interface IFileGenerator
{
Data GenerateFile();
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace ProxyInterfaceSourceGenerator.FileGenerators
{
internal interface IFilesGenerator
{
IEnumerable<Data> GenerateFiles();
}
}
@@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ProxyInterfaceSourceGenerator.Extensions;
using ProxyInterfaceSourceGenerator.FileGenerators;
using ProxyInterfaceSourceGenerator.Utils;
namespace ClassLibrarySourceGen
{
internal class PartialInterfacesGenerator : IFilesGenerator
{
private readonly GeneratorExecutionContext _context;
private readonly IDictionary<InterfaceDeclarationSyntax, string> _candidateInterfaces;
public PartialInterfacesGenerator(GeneratorExecutionContext context, IDictionary<InterfaceDeclarationSyntax, string> candidateInterfaces)
{
_context = context;
_candidateInterfaces = candidateInterfaces;
}
public IEnumerable<Data> GenerateFiles()
{
foreach (var x in _candidateInterfaces)
{
string interfaceName = $"I{x.Value.Split('.').Last()}";
yield return new Data
{
FileName = $"I{interfaceName}.cs",
Text = CreatePartialInterfaceCode(_context.Compilation.GetTypeByMetadataName(x.Value), interfaceName)
};
}
}
private string CreatePartialInterfaceCode(INamedTypeSymbol symbol, string interfaceName) => $@"using System;
namespace {symbol.ContainingNamespace}
{{
public partial interface {interfaceName}
{{
{GenerateSimpleProperties(symbol)}
{GenerateMethods(symbol)}
}}
}}";
private string GenerateSimpleProperties(INamedTypeSymbol symbol)
{
var str = new StringBuilder();
foreach (IPropertySymbol property in MemberHelper.GetPublicProperties(symbol, p => p.Type.IsValueType || p.Type.ToString() == "string"))
{
str.AppendLine($" {property.ToCode()}");
str.AppendLine();
}
return str.ToString();
}
private string GenerateMethods(INamedTypeSymbol symbol)
{
var str = new StringBuilder();
foreach (IMethodSymbol method in MemberHelper.GetPublicMethods(symbol))
{
str.AppendLine($" {method.ToCode()}");
str.AppendLine();
}
return str.ToString();
}
}
}
@@ -0,0 +1,30 @@
namespace ProxyInterfaceSourceGenerator.FileGenerators
{
internal class ProxyAttributeGenerator : IFileGenerator
{
private const string ClassName = "ProxyAttribute";
public Data GenerateFile()
{
return new Data
{
FileName = $"{ClassName}.cs",
Text = $@"using System;
namespace ProxyInterfaceGenerator
{{
[AttributeUsage(AttributeTargets.Interface)]
public class {ClassName} : Attribute
{{
public Type Type {{ get; }}
public {ClassName}(Type type)
{{
Type = type;
}}
}}
}}"
};
}
}
}
@@ -0,0 +1,130 @@
using System;
using System.Diagnostics;
using System.Text;
using ClassLibrarySourceGen;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using ProxyInterfaceSourceGenerator.FileGenerators;
namespace ProxyInterfaceSourceGenerator
{
[Generator]
public class ProxyInterfaceCodeGenerator : ISourceGenerator
{
private readonly ProxyAttributeGenerator _proxyAttributeGenerator = new ProxyAttributeGenerator();
public void Initialize(GeneratorInitializationContext context)
{
if (!Debugger.IsAttached)
{
Debugger.Launch();
}
context.RegisterForSyntaxNotifications(() => new ProxySyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
var attributeData = _proxyAttributeGenerator.GenerateFile();
context.AddSource(attributeData.FileName, SourceText.From(attributeData.Text, Encoding.UTF8));
if (context.SyntaxReceiver is not ProxySyntaxReceiver receiver)
{
return;
}
var p = context.Compilation.GetTypeByMetadataName("SourceGeneratorInterface.Person");
var ec = context.Compilation.GetTypeByMetadataName("Microsoft.CodeAnalysis.GeneratorExecutionContext");
var generator = new PartialInterfacesGenerator(context, receiver.CandidateInterfaces);
foreach (var data in generator.GenerateFiles())
{
context.AddSource(data.FileName, SourceText.From(data.Text, Encoding.UTF8));
}
}
}
public struct Test
{
public int Id { get; set; }
public Clazz C { get; }
}
public sealed class Clazz
{
public string Name { get; set; }
}
public interface ITest
{
int Id { get; set; }
IClazz C { get; }
}
public interface IClazz
{
string Name { get; set; }
}
public class TestMock : ITest
{
private Test _instance;
private IClazz _clazz;
public TestMock(Test instance)
{
_instance = instance;
_clazz = new ClazzMock(_instance.C);
}
public int Id
{
get => _instance.Id;
set => _instance.Id = value;
}
public IClazz C => _clazz;
}
public class ClazzMock : IClazz
{
private Clazz _instance;
public ClazzMock(Clazz instance)
{
_instance = instance;
}
public string Name
{
get => _instance.Name;
set => _instance.Name = value;
}
}
public interface IPrintable
{
int Length { get; }
int Count { get; }
void Print1();
void Print2();
}
public class PrinterV1 : IPrintable
{
public int Length => 100;
public int Count => 200;
public void Print1()
{
Console.WriteLine("PrinterV1 - Print1");
}
public void Print2()
{
Console.WriteLine("PrinterV1 - Print2");
}
}
}
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.10.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" PrivateAssets="all" />
</ItemGroup>
</Project>
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace ProxyInterfaceSourceGenerator
{
internal class ProxySyntaxReceiver : ISyntaxReceiver
{
public IDictionary<InterfaceDeclarationSyntax, string> CandidateInterfaces { get; } = new Dictionary<InterfaceDeclarationSyntax, string>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is InterfaceDeclarationSyntax interfaceDeclarationSyntax && TryGet(interfaceDeclarationSyntax, out string typeName))
{
CandidateInterfaces.Add(interfaceDeclarationSyntax, typeName);
}
}
private static bool TryGet(InterfaceDeclarationSyntax interfaceDeclarationSyntax, out string typeName)
{
typeName = null;
// TODO : how to check if the InterfaceDeclarationSyntax has 'partial' ?
var attrinbuteLists = interfaceDeclarationSyntax.AttributeLists.FirstOrDefault(x => x.Attributes.Any(a => a.Name.ToString().Equals("ProxyInterfaceGenerator.Proxy")));
if (attrinbuteLists == null)
{
return false;
}
var attributeSyntax = attrinbuteLists.Attributes.FirstOrDefault();
if (attributeSyntax == null)
{
return false;
}
var arg = attributeSyntax.ArgumentList.Arguments.First();
typeName = arg.Expression.ChildNodes().First().GetText().ToString();
return true;
}
}
}
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace ProxyInterfaceSourceGenerator.Utils
{
internal static class MemberHelper
{
private static string[] ExcludedMethods = new string[] { "ToString", "GetHashCode" };
public static IEnumerable<IPropertySymbol> GetPublicProperties(INamedTypeSymbol classSymbol, Func<IPropertySymbol, bool> filter)
{
return GetPublicMembers(classSymbol, p => p.Kind == SymbolKind.Property, filter);
}
public static IEnumerable<IMethodSymbol> GetPublicMethods(INamedTypeSymbol classSymbol, Func<IMethodSymbol, bool> filter = null)
{
if (filter == null)
{
filter = _ => true;
}
return GetPublicMembers(classSymbol,
m => m.Kind == SymbolKind.Method,
m => m.MethodKind == MethodKind.Ordinary,
m => !ExcludedMethods.Contains(m.Name),
filter);
}
private static IEnumerable<T> GetPublicMembers<T>(INamedTypeSymbol classSymbol, params Func<T, bool>[] filters) where T : ISymbol
{
var membersQuery = classSymbol.GetMembers().OfType<T>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public);
foreach (var filter in filters)
{
membersQuery = membersQuery.Where(filter);
}
var members = membersQuery.ToList();
var propertyNames = membersQuery.Select(x => x.Name);
var baseType = classSymbol.BaseType;
while (baseType != null)
{
var baseMembers = baseType.GetMembers().OfType<T>()
.Where(m => m.DeclaredAccessibility == Accessibility.Public)
.Where(x => !propertyNames.Contains(x.Name));
foreach (var filter in filters)
{
baseMembers = baseMembers.Where(filter);
}
members.AddRange(baseMembers);
baseType = baseType.BaseType;
}
return membersQuery;
}
}
}