diff --git a/ProxyInterfaceSourceGenerator Solution.sln b/ProxyInterfaceSourceGenerator Solution.sln
new file mode 100644
index 0000000..16fd633
--- /dev/null
+++ b/ProxyInterfaceSourceGenerator Solution.sln
@@ -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
diff --git a/src-examples/ProxyInterfaceConsumer/IPerson.cs b/src-examples/ProxyInterfaceConsumer/IPerson.cs
new file mode 100644
index 0000000..08336e2
--- /dev/null
+++ b/src-examples/ProxyInterfaceConsumer/IPerson.cs
@@ -0,0 +1,7 @@
+namespace SourceGeneratorInterface
+{
+ [ProxyInterfaceGenerator.Proxy(typeof(SourceGeneratorInterface.Person))]
+ public partial interface IPerson
+ {
+ }
+}
\ No newline at end of file
diff --git a/src-examples/ProxyInterfaceConsumer/Person.cs b/src-examples/ProxyInterfaceConsumer/Person.cs
new file mode 100644
index 0000000..363e59a
--- /dev/null
+++ b/src-examples/ProxyInterfaceConsumer/Person.cs
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/src-examples/ProxyInterfaceConsumer/Program.cs b/src-examples/ProxyInterfaceConsumer/Program.cs
new file mode 100644
index 0000000..5a82864
--- /dev/null
+++ b/src-examples/ProxyInterfaceConsumer/Program.cs
@@ -0,0 +1,11 @@
+namespace SourceGeneratorInterface
+{
+ public class Program
+ {
+ public static void Main()
+ {
+ IPerson ip = null;
+ //var i = ip.Id;
+ }
+ }
+}
diff --git a/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj b/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj
new file mode 100644
index 0000000..c914ec4
--- /dev/null
+++ b/src-examples/ProxyInterfaceConsumer/ProxyInterfaceConsumer.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net5.0
+ Exe
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProxyInterfaceSourceGenerator/Extensions/SymbolExtensions.cs b/src/ProxyInterfaceSourceGenerator/Extensions/SymbolExtensions.cs
new file mode 100644
index 0000000..645cbc7
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/Extensions/SymbolExtensions.cs
@@ -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();
+ foreach (var ps in method.Parameters)
+ {
+ parameters.Add($"{ps.Type} {ps.Name}");
+ }
+
+ return $"{method.ReturnType} {method.Name}({string.Join(", ", parameters)});";
+ }
+ }
+}
diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/Data.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/Data.cs
new file mode 100644
index 0000000..19f522c
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/Data.cs
@@ -0,0 +1,9 @@
+namespace ProxyInterfaceSourceGenerator.FileGenerators
+{
+ internal record Data
+ {
+ public string FileName { get; set; }
+
+ public string Text { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/IFileGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/IFileGenerator.cs
new file mode 100644
index 0000000..37c084a
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/IFileGenerator.cs
@@ -0,0 +1,7 @@
+namespace ProxyInterfaceSourceGenerator.FileGenerators
+{
+ internal interface IFileGenerator
+ {
+ Data GenerateFile();
+ }
+}
\ No newline at end of file
diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/IFilesGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/IFilesGenerator.cs
new file mode 100644
index 0000000..78a52fc
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/IFilesGenerator.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+
+namespace ProxyInterfaceSourceGenerator.FileGenerators
+{
+ internal interface IFilesGenerator
+ {
+ IEnumerable GenerateFiles();
+ }
+}
\ No newline at end of file
diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs
new file mode 100644
index 0000000..2e4e248
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/PartialInterfacesGenerator.cs
@@ -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 _candidateInterfaces;
+
+ public PartialInterfacesGenerator(GeneratorExecutionContext context, IDictionary candidateInterfaces)
+ {
+ _context = context;
+ _candidateInterfaces = candidateInterfaces;
+ }
+
+ public IEnumerable 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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs
new file mode 100644
index 0000000..763fc6b
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/FileGenerators/ProxyAttributeGenerator.cs
@@ -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;
+ }}
+ }}
+}}"
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs
new file mode 100644
index 0000000..15e9376
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceCodeGenerator.cs
@@ -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");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj
new file mode 100644
index 0000000..39620e9
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/ProxyInterfaceSourceGenerator.csproj
@@ -0,0 +1,17 @@
+
+
+
+ netstandard2.0
+ 9
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProxyInterfaceSourceGenerator/ProxySyntaxReceiver.cs b/src/ProxyInterfaceSourceGenerator/ProxySyntaxReceiver.cs
new file mode 100644
index 0000000..44de8e3
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/ProxySyntaxReceiver.cs
@@ -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 CandidateInterfaces { get; } = new Dictionary();
+
+ 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;
+ }
+ }
+}
diff --git a/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs b/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs
new file mode 100644
index 0000000..58b4662
--- /dev/null
+++ b/src/ProxyInterfaceSourceGenerator/Utils/MemberHelper.cs
@@ -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 GetPublicProperties(INamedTypeSymbol classSymbol, Func filter)
+ {
+ return GetPublicMembers(classSymbol, p => p.Kind == SymbolKind.Property, filter);
+ }
+
+ public static IEnumerable GetPublicMethods(INamedTypeSymbol classSymbol, Func 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 GetPublicMembers(INamedTypeSymbol classSymbol, params Func[] filters) where T : ISymbol
+ {
+ var membersQuery = classSymbol.GetMembers().OfType()
+ .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()
+ .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;
+ }
+ }
+}
\ No newline at end of file