Skip to content

Commit bbbd0ac

Browse files
author
Laszlo Deak
committed
Add project files.
1 parent d3df0a6 commit bbbd0ac

9 files changed

+303
-0
lines changed

ProtobufSourceGenerator.sln

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.5.33209.295
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtobufSourceGenerator", "ProtobufSourceGenerator\ProtobufSourceGenerator.csproj", "{6AEBF24B-6F08-431F-AFDE-A1BF1E47DF2A}"
7+
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "SampleApp\SampleApp.csproj", "{6154F071-AE91-413F-B0BE-3E8E3BFDCF1E}"
9+
EndProject
10+
Global
11+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
12+
Debug|Any CPU = Debug|Any CPU
13+
Release|Any CPU = Release|Any CPU
14+
EndGlobalSection
15+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{6AEBF24B-6F08-431F-AFDE-A1BF1E47DF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17+
{6AEBF24B-6F08-431F-AFDE-A1BF1E47DF2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
18+
{6AEBF24B-6F08-431F-AFDE-A1BF1E47DF2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
19+
{6AEBF24B-6F08-431F-AFDE-A1BF1E47DF2A}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{6154F071-AE91-413F-B0BE-3E8E3BFDCF1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{6154F071-AE91-413F-B0BE-3E8E3BFDCF1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{6154F071-AE91-413F-B0BE-3E8E3BFDCF1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{6154F071-AE91-413F-B0BE-3E8E3BFDCF1E}.Release|Any CPU.Build.0 = Release|Any CPU
24+
EndGlobalSection
25+
GlobalSection(SolutionProperties) = preSolution
26+
HideSolutionNode = FALSE
27+
EndGlobalSection
28+
GlobalSection(ExtensibilityGlobals) = postSolution
29+
SolutionGuid = {C81B2C7D-DB65-4A1D-80AF-96017889BE38}
30+
EndGlobalSection
31+
EndGlobal
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp.Syntax;
3+
4+
namespace ProtobufSourceGenerator;
5+
6+
public class PropertyInfo
7+
{
8+
public PropertyInfo(ClassDeclarationSyntax classDeclaration, PropertyDeclarationSyntax property, INamedTypeSymbol typeSymbol, IPropertySymbol propertySymbol)
9+
{
10+
ClassDeclaration = classDeclaration;
11+
Property = property;
12+
TypeSymbol = typeSymbol;
13+
PropertySymbol = propertySymbol;
14+
}
15+
16+
public ClassDeclarationSyntax ClassDeclaration { get; }
17+
public PropertyDeclarationSyntax Property { get; }
18+
public INamedTypeSymbol TypeSymbol { get; }
19+
public IPropertySymbol PropertySymbol { get; }
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Text;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
7+
namespace ProtobufSourceGenerator;
8+
9+
public class ProtoClassGenerator
10+
{
11+
public IEnumerable<(string, string)> CreateClasses(IEnumerable<PropertyInfo> propertyShadows) => propertyShadows.GroupBy(x => x.TypeSymbol.Name).Select(x => (x.Key, CreateClass(x)));
12+
13+
private string CreateClass(IEnumerable<PropertyInfo> propertyShadows)
14+
{
15+
var classInfo = propertyShadows.First().TypeSymbol;
16+
17+
StringBuilder sb = new();
18+
sb.AppendLine($"namespace {classInfo.ContainingNamespace};");
19+
sb.AppendLine();
20+
21+
var classSyntax = SyntaxFactory.ClassDeclaration(classInfo.Name)
22+
.WithModifiers(
23+
SyntaxFactory.TokenList(
24+
new[] {
25+
SyntaxFactory.Token(SyntaxKind.PublicKeyword),
26+
SyntaxFactory.Token(SyntaxKind.PartialKeyword)
27+
}));
28+
29+
int counter = 1;
30+
foreach (var shadow in propertyShadows)
31+
{
32+
if (shadow.Property.AccessorList.Accessors.All(x => x.Body == null && x.ExpressionBody == null))
33+
{
34+
string typeName = GetTypeName(shadow);
35+
var newProperty = SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName(typeName),
36+
SyntaxFactory.Identifier($"Proto{shadow.Property.Identifier.Text}"))
37+
.WithModifiers(SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)));
38+
39+
var getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
40+
.WithExpressionBody(SyntaxFactory.ArrowExpressionClause(SyntaxFactory.IdentifierName(shadow.Property.Identifier.Text)))
41+
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
42+
43+
var setter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
44+
.WithExpressionBody(SyntaxFactory.ArrowExpressionClause(SyntaxFactory.AssignmentExpression(
45+
SyntaxKind.SimpleAssignmentExpression,
46+
SyntaxFactory.IdentifierName(shadow.Property.Identifier.Text),
47+
SyntaxFactory.IdentifierName("value"))))
48+
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
49+
50+
var protoMemberAttribute = SyntaxFactory.SingletonList(
51+
SyntaxFactory.AttributeList(
52+
SyntaxFactory.SingletonSeparatedList(
53+
SyntaxFactory.Attribute(
54+
SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("Protobuf"), SyntaxFactory.IdentifierName("ProtoMember")))
55+
.WithArgumentList(
56+
SyntaxFactory.AttributeArgumentList(
57+
SyntaxFactory.SingletonSeparatedList(
58+
SyntaxFactory.AttributeArgument(
59+
SyntaxFactory.LiteralExpression(
60+
SyntaxKind.NumericLiteralExpression,
61+
SyntaxFactory.Literal(counter++)))))))));
62+
63+
newProperty = newProperty.WithAccessorList(SyntaxFactory.AccessorList(SyntaxFactory.List(new[] { getter, setter })))
64+
.NormalizeWhitespace().WithLeadingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.Whitespace(" ")))
65+
.WithAttributeLists(protoMemberAttribute);
66+
67+
classSyntax = classSyntax.WithMembers(classSyntax.Members.Add(newProperty));
68+
}
69+
}
70+
sb.Append(classSyntax.NormalizeWhitespace().ToFullString());
71+
return sb.ToString();
72+
}
73+
74+
private static string GetTypeName(PropertyInfo shadow)
75+
{
76+
if (shadow.PropertySymbol.Type is INamedTypeSymbol propertyType)
77+
return GetTypeName(propertyType);
78+
return shadow.PropertySymbol.Type.Name;
79+
}
80+
81+
private static string GetTypeName(INamedTypeSymbol type)
82+
{
83+
if (!type.IsGenericType)
84+
return $"{type.ContainingNamespace}.{type.Name}";
85+
86+
StringBuilder sb = new();
87+
sb.Append($"{type.ContainingNamespace}{type.Name}<");
88+
89+
for (int i = 0; i < type.TypeArguments.Length; i++)
90+
{
91+
var typeArgument = type.TypeArguments[i];
92+
if (typeArgument is INamedTypeSymbol namedType)
93+
sb.Append(GetTypeName(namedType));
94+
else
95+
sb.Append(typeArgument.Name);
96+
97+
if (i < type.TypeArguments.Length - 1)
98+
sb.Append(", ");
99+
}
100+
sb.Append(">");
101+
return sb.ToString();
102+
}
103+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections.Generic;
2+
using Microsoft.CodeAnalysis;
3+
4+
namespace ProtobufSourceGenerator;
5+
6+
[Generator]
7+
public class ProtoGenerator : ISourceGenerator
8+
{
9+
public void Execute(GeneratorExecutionContext context)
10+
{
11+
var compilation = context.Compilation;
12+
List<PropertyInfo> propertyShadowInfos = new();
13+
foreach (var tree in compilation.SyntaxTrees)
14+
{
15+
var root = tree.GetRoot();
16+
var walker = new ProtoSyntaxTreeWalker(context.Compilation.GetSemanticModel(tree));
17+
propertyShadowInfos.AddRange(walker.Analyze(root));
18+
}
19+
20+
var classGenerator = new ProtoClassGenerator();
21+
foreach (var (fileName, source) in classGenerator.CreateClasses(propertyShadowInfos))
22+
context.AddSource($"Proto{fileName}.g.cs", source);
23+
}
24+
25+
public void Initialize(GeneratorInitializationContext context)
26+
{
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Microsoft.CodeAnalysis;
4+
using Microsoft.CodeAnalysis.CSharp;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
7+
namespace ProtobufSourceGenerator;
8+
9+
public class ProtoSyntaxTreeWalker : CSharpSyntaxWalker
10+
{
11+
private readonly SemanticModel _semantics;
12+
private List<PropertyInfo> _properties;
13+
private bool _collectingProperties;
14+
private ClassDeclarationSyntax _currentClass;
15+
16+
public ProtoSyntaxTreeWalker(SemanticModel model)
17+
{
18+
_semantics = model;
19+
}
20+
21+
public IEnumerable<PropertyInfo> Analyze(SyntaxNode root)
22+
{
23+
_properties = new();
24+
_collectingProperties = false;
25+
_currentClass = null;
26+
Visit(root);
27+
var result = _properties;
28+
_properties = null;
29+
_currentClass = null;
30+
return result;
31+
}
32+
33+
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
34+
{
35+
_collectingProperties = false;
36+
_currentClass = null;
37+
if (node != null && node.Modifiers.Any(x => x.IsKeyword() && x.IsKind(SyntaxKind.PartialKeyword)))
38+
{
39+
foreach (var attributeList in node.AttributeLists)
40+
{
41+
if (attributeList.Attributes.Any(x => x.Name.ToFullString().Contains("ProtoContract")
42+
&& _semantics.GetSymbolInfo(x).Symbol is IMethodSymbol symbol
43+
&& symbol.ContainingType.ToString() == "Protobuf.ProtoContractAttribute"))
44+
{
45+
_collectingProperties = true;
46+
_currentClass = node;
47+
}
48+
}
49+
}
50+
base.VisitClassDeclaration(node);
51+
}
52+
53+
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
54+
{
55+
if (_collectingProperties && _currentClass != null)
56+
{
57+
var typeSymbol = _semantics.GetDeclaredSymbol(_currentClass);
58+
var propertySymbol = _semantics.GetDeclaredSymbol(node);
59+
if (propertySymbol.GetMethod != null && !propertySymbol.GetMethod.IsReadOnly
60+
&& propertySymbol.SetMethod != null && !propertySymbol.SetMethod.IsReadOnly)
61+
_properties.Add(new PropertyInfo(_currentClass, node, typeSymbol, propertySymbol));
62+
}
63+
base.VisitPropertyDeclaration(node);
64+
}
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<LangVersion>latest</LangVersion>
6+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" PrivateAssets="all" />
11+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<None Include="$(OutputPath)\$(AssemblyName)" Pack="true" PackagePath="analyzer/dotnet/cs" Visible="false" />
16+
</ItemGroup>
17+
18+
</Project>

SampleApp/Entity.cs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using ProtoBuf;
2+
3+
namespace SampleApp;
4+
5+
[ProtoContract]
6+
public partial class Entity
7+
{
8+
public int Id { get; set; }
9+
10+
public string Value { get; set; }
11+
}

SampleApp/Program.cs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+

2+
using ProtoBuf;
3+
using SampleApp;
4+
5+
using var ms = new MemoryStream();
6+
Serializer.Serialize(ms, new Entity() { Id = 1, Value = "hello world" });
7+
ms.Seek(0, SeekOrigin.Begin);
8+
var entity = Serializer.Deserialize<Entity>(ms);
9+
Console.WriteLine(entity.Value);

SampleApp/SampleApp.csproj

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net7.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="protobuf-net" Version="3.1.26" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<ProjectReference Include="..\ProtobufSourceGenerator\ProtobufSourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
16+
</ItemGroup>
17+
18+
</Project>

0 commit comments

Comments
 (0)