From 84af9523003d25a6b744df45d220a284e926131a Mon Sep 17 00:00:00 2001 From: JacobTech Date: Sun, 4 Jun 2023 13:32:25 -0400 Subject: [PATCH] Source Generation This is v1 of the new source generator. --- .gitignore | 94 +++++++++++++++ ServerDatabase.SourceGenerator.sln | 16 +++ .../ServerDatabase.SourceGenerator.csproj | 31 +++++ .../TableRowAttribute.cs | 12 ++ .../TableRowGenerator.cs | 109 ++++++++++++++++++ 5 files changed, 262 insertions(+) create mode 100644 .gitignore create mode 100644 ServerDatabase.SourceGenerator.sln create mode 100644 ServerDatabase.SourceGenerator/ServerDatabase.SourceGenerator.csproj create mode 100644 ServerDatabase.SourceGenerator/TableRowAttribute.cs create mode 100644 ServerDatabase.SourceGenerator/TableRowGenerator.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4375a6 --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/ServerDatabase.SourceGenerator.sln b/ServerDatabase.SourceGenerator.sln new file mode 100644 index 0000000..196354d --- /dev/null +++ b/ServerDatabase.SourceGenerator.sln @@ -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 diff --git a/ServerDatabase.SourceGenerator/ServerDatabase.SourceGenerator.csproj b/ServerDatabase.SourceGenerator/ServerDatabase.SourceGenerator.csproj new file mode 100644 index 0000000..2157e1f --- /dev/null +++ b/ServerDatabase.SourceGenerator/ServerDatabase.SourceGenerator.csproj @@ -0,0 +1,31 @@ + + + + enable + latestmajor + enable + netstandard2.0 + true + 1.0.0 + Server Database Source Generator + JacobTech + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + diff --git a/ServerDatabase.SourceGenerator/TableRowAttribute.cs b/ServerDatabase.SourceGenerator/TableRowAttribute.cs new file mode 100644 index 0000000..54d4293 --- /dev/null +++ b/ServerDatabase.SourceGenerator/TableRowAttribute.cs @@ -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; } +} \ No newline at end of file diff --git a/ServerDatabase.SourceGenerator/TableRowGenerator.cs b/ServerDatabase.SourceGenerator/TableRowGenerator.cs new file mode 100644 index 0000000..7f1050f --- /dev/null +++ b/ServerDatabase.SourceGenerator/TableRowGenerator.cs @@ -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 classWithAttributes = context.Compilation.SyntaxTrees.Where(st => st.GetRoot().DescendantNodes().OfType() + .Any(p => p.DescendantNodes().OfType().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().Where(cd => cd.DescendantNodes().OfType().Any())) + { + if (declaredClass is null) continue; + List? nodes = declaredClass + .DescendantNodes() + .OfType() + .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().First().Name.ToString(), declaredClass); + + foreach(IPropertySymbol? classProperty in relatedClass.Type.GetMembers().OfType()) + { + 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( + @" } +}"); + } +} \ No newline at end of file