Source Generation

This is v1 of the new source generator.
This commit is contained in:
JacobTech 2023-06-04 13:32:25 -04:00
parent db04b3b356
commit 84af952300
5 changed files with 262 additions and 0 deletions

94
.gitignore vendored Normal file
View File

@ -0,0 +1,94 @@
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/**/.idea/**
.idea/**/.idea/
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerDatabase.SourceGenerator", "ServerDatabase.SourceGenerator\ServerDatabase.SourceGenerator.csproj", "{2F4102DB-3F1D-463F-AC27-EA56616DA96D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2F4102DB-3F1D-463F-AC27-EA56616DA96D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F4102DB-3F1D-463F-AC27-EA56616DA96D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F4102DB-3F1D-463F-AC27-EA56616DA96D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F4102DB-3F1D-463F-AC27-EA56616DA96D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latestmajor</LangVersion>
<Nullable>enable</Nullable>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeBuildOutput>true</IncludeBuildOutput>
<Version>1.0.0</Version>
<Title>Server Database Source Generator</Title>
<Authors>JacobTech</Authors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
</ItemGroup>
<ItemGroup>
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
</ItemGroup>
<Target Name="CustomActionsAfterPack" AfterTargets="Pack">
<Message Text="Actions AfterPack: $(PackageId).$(PackageVersion).nupkg" Importance="high" />
<Exec Command="nuget push -Source https://nuget.jacobtech.com/v3/index.json bin/Release/$(PackageId).$(PackageVersion).nupkg" />
</Target>
</Project>

View File

@ -0,0 +1,12 @@
namespace ServerDatabase.SourceGenerator;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public sealed class TableRowAttribute : Attribute
{
public TableRowAttribute(Type type)
{
this.Type = type;
}
public Type Type { get; private set; }
}

View File

@ -0,0 +1,109 @@
using System.Diagnostics;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using TypeInfo = Microsoft.CodeAnalysis.TypeInfo;
namespace ServerDatabase.SourceGenerator;
[Generator]
public class TableRowGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
#if DEBUG
if (!Debugger.IsAttached) Debugger.Launch();
#endif
}
public void Execute(GeneratorExecutionContext context)
{
try
{
INamedTypeSymbol? attributeSymbol = context.Compilation.GetTypeByMetadataName(typeof(TableRowAttribute).FullName);
IEnumerable<SyntaxTree> classWithAttributes = context.Compilation.SyntaxTrees.Where(st => st.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>()
.Any(p => p.DescendantNodes().OfType<AttributeSyntax>().Any()));
foreach (SyntaxTree? tree in classWithAttributes)
{
if (tree is null) continue;
SemanticModel semanticModel = context.Compilation.GetSemanticModel(tree);
foreach (ClassDeclarationSyntax? declaredClass in tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().Where(cd => cd.DescendantNodes().OfType<AttributeSyntax>().Any()))
{
if (declaredClass is null) continue;
List<SyntaxToken>? nodes = declaredClass
.DescendantNodes()
.OfType<AttributeSyntax>()
.FirstOrDefault(a => a.DescendantTokens().Any(dt => dt.IsKind(SyntaxKind.IdentifierToken) && dt.Parent is not null && semanticModel.GetTypeInfo(dt.Parent).Type is not null && semanticModel.GetTypeInfo(dt.Parent).Type.Name == attributeSymbol.Name))
?.DescendantTokens()
?.Where(dt => dt.IsKind(SyntaxKind.IdentifierToken))
?.ToList();
if(nodes == null)
{
continue;
}
TypeInfo relatedClass = semanticModel.GetTypeInfo(nodes.Last().Parent);
StringBuilder generatedClass = this.GenerateClass(tree.GetRoot().DescendantNodes().OfType<FileScopedNamespaceDeclarationSyntax>().First().Name.ToString(), declaredClass);
foreach(IPropertySymbol? classProperty in relatedClass.Type.GetMembers().OfType<IPropertySymbol>())
{
this.GenerateProperty(classProperty, ref generatedClass);
}
this.CloseClass(generatedClass);
context.AddSource($"{declaredClass.Identifier}_{relatedClass.Type.Name}.g.cs", SourceText.From(generatedClass.ToString(), Encoding.UTF8));
}
}
}
catch (Exception e)
{
context.AddSource("teste.g.cs", SourceText.From(@$"using System;
namespace ServerDatabase.SourceGenerator.Generated
{{
public class bobe
{{
public string e = @""{e}"";
}}
}}", Encoding.UTF8));
}
return;
}
private void GenerateProperty(IPropertySymbol prop, ref StringBuilder builder)
{
string bad = prop.Type.ToString();
bad = bad.Replace("ServerDatabase.TableColumn<", "");
bad = bad.Remove(bad.LastIndexOf('>'));
builder.AppendLine($" public {bad} {prop.Name} {{ get; set; }}");
}
private StringBuilder GenerateClass(string n, ClassDeclarationSyntax og)
{
StringBuilder sb = new StringBuilder();
sb.Append(@$"using System;
namespace {n}
{{
public partial class " + og.Identifier);
sb.Append(@"
{
");
return sb;
}
private void CloseClass(StringBuilder generatedClass)
{
generatedClass.Append(
@" }
}");
}
}