From 6e6577e760961a66cac327c9c9be7c483c84d2e7 Mon Sep 17 00:00:00 2001 From: Aydin Erdas Date: Sun, 10 Mar 2024 11:05:27 +0100 Subject: [PATCH] Added ConstantFromJson for https://github.com/Lombiq/Helpful-Libraries/issues/238 --- .../Examples.cs | 16 ++ ...ulLibraries.SourceGenerators.Sample.csproj | 17 ++ .../package.json | 23 ++ .../ConstantFromJsonGenerator.cs | 259 ++++++++++++++++++ .../License.md | 13 + ...q.HelpfulLibraries.SourceGenerators.csproj | 27 ++ .../NuGetIcon.png | Bin 0 -> 4657 bytes .../Properties/launchSettings.json | 9 + .../Readme.md | 51 ++++ Lombiq.HelpfulLibraries.sln | 12 + 10 files changed, 427 insertions(+) create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/Examples.cs create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/Lombiq.HelpfulLibraries.SourceGenerators.Sample.csproj create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/package.json create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/ConstantFromJsonGenerator.cs create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/License.md create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.csproj create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/NuGetIcon.png create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Properties/launchSettings.json create mode 100644 Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Readme.md diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/Examples.cs b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/Examples.cs new file mode 100644 index 00000000..9357c7cd --- /dev/null +++ b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/Examples.cs @@ -0,0 +1,16 @@ +using Generators; +using System; + +namespace Lombiq.HelpfulLibraries.SourceGenerators.Sample; + +[ConstantFromJson("GulpUglifyVersion", "package.json", "gulp-uglify")] +[ConstantFromJson("GulpVersion", "package.json", "gulp")] +public partial class Examples +{ + // Show usage of the generated constants + public void LogVersions() + { + Console.WriteLine(GulpUglifyVersion); + Console.WriteLine(GulpVersion); + } +} diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/Lombiq.HelpfulLibraries.SourceGenerators.Sample.csproj b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/Lombiq.HelpfulLibraries.SourceGenerators.Sample.csproj new file mode 100644 index 00000000..cc00a341 --- /dev/null +++ b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/Lombiq.HelpfulLibraries.SourceGenerators.Sample.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + Lombiq.HelpfulLibraries.SourceGenerators.Sample + + + + + + + + + + + diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/package.json b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/package.json new file mode 100644 index 00000000..9d038ca3 --- /dev/null +++ b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.Sample/package.json @@ -0,0 +1,23 @@ +{ + "private": true, + "devDependencies": { + "fs": "0.0.2", + "glob": "5.0.15", + "path-posix": "1.0.0", + "merge-stream": "1.0.0", + "gulp-if": "2.0.0", + "gulp": "3.9.0", + "gulp-newer": "0.5.1", + "gulp-plumber": "1.0.1", + "gulp-sourcemaps": "1.6.0", + "gulp-less": "3.0.3", + "gulp-autoprefixer": "2.2.0", + "gulp-minify-css": "1.2.1", + "gulp-typescript": "2.9.2", + "gulp-uglify": "1.4.1", + "gulp-rename": "1.2.2", + "gulp-concat": "2.6.0", + "gulp-header": "1.7.1" + }, + "dependencies": { } +} \ No newline at end of file diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/ConstantFromJsonGenerator.cs b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/ConstantFromJsonGenerator.cs new file mode 100644 index 00000000..8dd20eab --- /dev/null +++ b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/ConstantFromJsonGenerator.cs @@ -0,0 +1,259 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Text.Json; + +namespace Lombiq.HelpfulLibraries.SourceGenerators; + +/// +/// A generator that exposes a value from a JSON file at compile time. +/// The target class should be annotated with the 'Generators.ConstantFromJsonAttribute' attribute. +/// +[Generator] +public class ConstantFromJsonGenerator : IIncrementalGenerator +{ + private const string Namespace = "Generators"; + private const string AttributeName = "ConstantFromJsonAttribute"; + + private const string AttributeSourceCode = $@"// + +namespace {Namespace} +{{ + [System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)] + public class {AttributeName} : System.Attribute + {{ + public string Value {{ get; }} + public {AttributeName}(string constantName, string fileName, string jsonPath) + {{ + Value = ""testvaluetje""; + }} + }} +}}"; + + private readonly Dictionary _fileContents = []; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // Add the marker attribute to the compilation. + context.RegisterPostInitializationOutput(ctx => ctx.AddSource( + $"{AttributeName}.g.cs", + SourceText.From(AttributeSourceCode, Encoding.UTF8))); + + // Filter classes annotated with the [ConstantFromJson] attribute. + // Only filtered Syntax Nodes can trigger code generation. + var provider = context.SyntaxProvider + .CreateSyntaxProvider( + (s, _) => s is ClassDeclarationSyntax, + (ctx, _) => GetClassDeclarationForSourceGen(ctx)) + .Where(t => t.reportAttributeFound) + .Select((t, _) => (t.Item1, t.Item3)); + + var additionalFiles = context.AdditionalTextsProvider + .Where(static file => file.Path.EndsWith(".json", StringComparison.OrdinalIgnoreCase)); + + var namesAndContents = additionalFiles + .Select((file, cancellationToken) => + (Content: file.GetText(cancellationToken)?.ToString(), + file.Path)); + + context.RegisterSourceOutput(namesAndContents.Collect(), (_, contents) => + { + foreach ((string? content, string path) in contents) + { + // Add to the dictionary + _fileContents.Add(path, content ?? string.Empty); + } + }); + + // Generate the source code. + context.RegisterSourceOutput( + context.CompilationProvider.Combine(provider.Collect()), + (ctx, t) => GenerateCode(ctx, t.Left, t.Right)); + } + + /// + /// Checks whether the Node is annotated with the [ConstantFromJson] attribute and maps syntax context to + /// the specific node type (ClassDeclarationSyntax). + /// + /// Syntax context, based on CreateSyntaxProvider predicate + /// The specific cast and whether the attribute was found. + private static (ClassDeclarationSyntax, bool reportAttributeFound, List>) + GetClassDeclarationForSourceGen( + GeneratorSyntaxContext context) + { + var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; + var attributesData = new List>(); + + // Go through all attributes of the class. + foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists) + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) + { + continue; // if we can't get the symbol, ignore it + } + + string attributeName = attributeSymbol.ContainingType.ToDisplayString(); + // Check the full name of the [ConstantFromJson] attribute. + if (attributeName != $"{Namespace}.{AttributeName}") + { + continue; + } + + var arguments = new Dictionary(); + int idx = 0; + foreach (var argumentSyntax in attributeSyntax.ArgumentList?.Arguments!) + { + if (argumentSyntax.Expression is LiteralExpressionSyntax literalExpression) + arguments.Add(attributeSymbol.Parameters[idx].Name, literalExpression.Token.Text); + + idx += 1; + } + + attributesData.Add(arguments); + } + + return (classDeclarationSyntax, attributesData.Count > 0, attributesData); + } + + /// + /// Generate code action. + /// It will be executed on specific nodes (ClassDeclarationSyntax annotated with the [ConstantFromJson] attribute) + /// changed by the user. + /// + /// Source generation context used to add source files. + /// Compilation used to provide access to the Semantic Model. + /// + /// Nodes annotated with the [ConstantFromJson] attribute that trigger the + /// generate action. + /// + private void GenerateCode(SourceProductionContext context, Compilation compilation, + ImmutableArray<(ClassDeclarationSyntax, List>)> classDeclarations) + { + // Go through all filtered class declarations. + foreach (var (classDeclarationSyntax, attributeData) in classDeclarations) + { + // We need to get semantic model of the class to retrieve metadata. + var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree); + + // Symbols allow us to get the compile-time information. + if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax, cancellationToken: context.CancellationToken) + is not INamedTypeSymbol classSymbol) + { + continue; + } + + string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); + + // 'Identifier' means the token of the node. Get class name from the syntax node. + string className = classDeclarationSyntax.Identifier.Text; + + string partialBody = string.Empty; + + // It's possible that a single class is annotated with our marker attribute multiple times + foreach (var dictionary in attributeData) + { + // Get values from dictionary + string? constantName = dictionary["constantName"]; + string? fileName = dictionary["fileName"]; + string? jsonPath = dictionary["jsonPath"]; + + // Try get content of file from dictionary where key ends with filename + var fileContent = _fileContents + .FirstOrDefault(kvp => + kvp.Key.EndsWith(fileName.Replace($"\"", string.Empty), StringComparison.Ordinal)); + + // If the file content is empty, skip + if (string.IsNullOrEmpty(fileContent.Value)) + { + return; + } + + var jsonDocument = JsonDocument.Parse(fileContent.Value); + + // try to find the value in the jsonDocument + var jsonValue = FindProperty(jsonDocument.RootElement, jsonPath.Replace("\"", "")); + + if (jsonValue == null) + { + return; + } + + partialBody += $""" + public const string {constantName.Replace("\"", "")} = "{jsonValue.Value}"; + """; + } + + // Create a new partial class with the same name as the original class. + // Build up the source code + string code = $@"// + +using System; +using System.Collections.Generic; + +namespace {namespaceName}; + +partial class {className} +{{ + {partialBody} +}} +"; + // Add the source code to the compilation. + context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8)); + } + } + + /// + /// Find a property in a JSON document recursively. + /// + /// The JSON element to search in. + /// The property name to look for + private static JsonElement? FindProperty(JsonElement element, string propertyName) + { + foreach (var property in element.EnumerateObject()) + { + if (property.Name == propertyName) + { + return property.Value; + } + + switch (property.Value.ValueKind) + { + case JsonValueKind.Object: + { + var result = FindProperty(property.Value, propertyName); + if (result != null) + { + return result; + } + + break; + } + + case JsonValueKind.Array: + { + foreach (var arrayElement in property.Value.EnumerateArray()) + { + if (arrayElement.ValueKind == JsonValueKind.Object) + { + var result = FindProperty(arrayElement, propertyName); + if (result != null) + { + return result; + } + } + } + + break; + } + } + } + + return null; + } +} diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/License.md b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/License.md new file mode 100644 index 00000000..f516dbf7 --- /dev/null +++ b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/License.md @@ -0,0 +1,13 @@ +Copyright © 2011, [Lombiq Technologies Ltd.](https://lombiq.com) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +- Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.csproj b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.csproj new file mode 100644 index 00000000..133e66a9 --- /dev/null +++ b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators.csproj @@ -0,0 +1,27 @@ + + + + netstandard2.0 + false + enable + latest + + true + true + + Lombiq.HelpfulLibraries.SourceGenerators + Lombiq.HelpfulLibraries.SourceGenerators + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/NuGetIcon.png b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/NuGetIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..162a00508d8041833604d427216238c1b23b2d47 GIT binary patch literal 4657 zcmV-163*?3P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!Thn=~eB5))%gn$#O#QK7n$s4=L-h!O=vF^a;>Ff(Vme>2-ILt$p`^By>#th3Jf zo%mwTcYcq(e|sM=Ffi!HnCMv&IXStx>whD37Ty5YY(6YhyZU* z`lOOYjz@!+Y#0L27(o!^cm}spNxZ;7dL0%+0GcCv2#O=eyx?Vc`gIPV2NX&{?xE+f z^lJ~Gha`}M044qbL+Cjyh5$6L0{q&ILJ^D{pl{-(=dc(#fX0vrAa*1$6xZoFEQSZr z7(wvK@eI17ssIcRAOVts$TB0_jdpqtOTYI3UN3XFj3f*Ju%dMh;Pd&EP@s_mL<&Rz zN`wG)1dx-Hi@zU-9u4$`Y9Szs9MQo`12K>gq(l@_Ed*c%po9zSHwZo@h5^*`01;mT z`ZTTr{2)t5P^2*{ z(gugaA-4-MJOC@WCeUuzc2!$!Tr9nk4OKlrL<=A$CPveRp~&DtgXxuQsOkYCIs_yq zi&{?^z~AA7u04mq{oY>iHJqn+`58HKgxn#(@Bplg966F+@!_re3a*ts4jq4d89JYP z9onnbLFfOxj>C%dSFkgE`n~i@HjEsAm1)zb(JOZRolfXjw;uf0ZqPdf^4#6;?gjV% z*U%g6Oqw*&Muh00rW_a^fQ3PW2Eoi3GpNOmYs&{9eADxn%=PE(0fF#fA}n9NT+Rsz zbv!^$PAKcLm^UY6aK!J=FSVgm12cYx7^I#ZE!NeYVupRJo=lRr3H? zKy$4{*9(~ZJ^0`Q)MCfEVJmLwL9L!~y}JhlA`~9XX0xfHqCze&pqc=%6)fU5hC)h8 zN+3F#uaU{~X&rd}cAQ!v@c5u}JrN2gd3pER#*ZH_M=qdR5*U#sU|L!l+<*TZYBA#T zfs^B*dJY{A2!;oe3c%X6k6=n5)f1>DfQU-~@k4&(k!l=&r+3(JzrPQBjV;t-BOn;g zUW1H`3~OOwq10SJwGa?-0+=;x7AdDH$H1CrGW$J@o54 zdjZ@Z9H16IC>-a;9n@lH$&$sEl#~>)p}-J8cZLri4vQ8oq!v4!FT6=qAZ@xH1%yK4 zAu1}$OhSQ}WY9&9uHYpb{UQWZRjuJ3^yxZy1iVMBmOMCLd^;d49`3wzf-OHkkLNdWRRBnz7Qp=Z^I`PpQPg5X zXuAroH~va3`9MLTuz0DgtTfqdHr7N$RS%%+93UYf0ZK|%Qi~nu%kO~S$=p`Y1qurV z#>3E|L#+!IEMSf6h6lJwX=y3M$Mb2_d#VAtb|0pe{GhNOXG>Zs~8~_gd-5!`6aMlE#+CYv~L$Id; z=GCiLbx#KSR|vq>y%iPZ)M7_WtCrGW=MV~$hgq{`Su-;;Z_Qv;6M!y40JeDz9?ZAA z+Sk|&?)N{YmVWTJ6R$*s$pZ;{CK~?G6X3zE>WK9K!-frm;^IZrVkgk9E&6r6%7Vf~ zf$}hZe3otI%o&&q;HVOJJsYtCShI$|&CC0FJ$ODnK`k9Zgo$0qysTcm+C)B!>%8q? zNJI)CJ3AYfQc;T?&tZn8T^i&4q>jWXzUO$`ZMRuVOH1%|^4vsR4uG?KxVlFSzGl8r zH!bkHxUfnSdjEAUs`P9-M=0OP%H8+o1 z+^FUOu=wVica=z9ULGm^rcg^7jvvnjYt|TQ=?9bD3b8+30Vd8V?ORYy02qYj-rTTP z#I||$ip>gfkF5gxy?lLxN@7k0Jp?*a&jLf zr{4rE`Eih#zh(0#(R=x~IIlp>r4!KD(F$I{2g%V1aA)FZm_QCu9>wDY*VdijdUGfE zUF^%oZFhYa9OVm%fWAZT@Nu!z4WD0b51a)*KvJ{~?nsJ*F$q0}C+r4_AwM7AQX_&G z0sN7i{w-+9PkDK{sLa2qvkjg-^)Bpd{hWMY_<4Mr^Z~=*;q*B$HCfy@5qk*TAJu~A zz-Qq7vJraf;x!LW2vipKSve%j7dK;x5B#mu&2aeQ)gIp}ojSk)3(|%{)}U|g(~Vn{ z4a9P);O|Nlx`W$Ti0vxA;vt^ngLAF$^Zh4aN5e(#hmUMK(-QC;1W^?LqKhg(Y@64fmZR|5m9MD9 zkIyf__Vcw+{P7d;{HYyq-6L*GSRODx+23>p{`0^|*izpFH^RJsAQUdI09#4EKoC^{ zAiD4XMMXup>Xlk-boo5+3`zRrz)k+FjSahC(Z1ioYiD)^Y$f=t^&0%D<`iuBq7m9$ zTpN1!%7Nx9aO`q>caw-+k=C%3k9%v>dpPaONobYV@4p>Y)!bi<~8#{*L{BSzO8=-{r@LW23?v&YVgRxFnQNCJrYldK^C-`;^+y76$C#Oznj@b9_DN-O#E zA3OR=AV2W>r7a>kNWP)JIaCi{UgcZ%#RJJ7_})}?3%nixw}z2zc1CQQ*Yo6C>h#Iu zLPLT1!DC1M9LN>Ku>0%Fu>N=>N%+E-brMl636%9cLDkh&+^=q}yLt{jZ2FX1%Cq<4 z=kUzQ?bKoiNAWhEI^W}9jsmDA0NLU{Zs3d?IrHEr|NZI2w!m3U-Z-}p4qZM;t?*!G zzn&y|%75|yf#{{qng{?#`fv+(UjEwj34C$w9JSN~pW~A!-ewL3?|jt)?Oo!_I}xbn z0c1%4@#PKp5(gffJ~ye=wcFI8vEvf#ZaG4&-oXdI_kz4TjWZBAApl?5itGA$d6i`S zm)z=)*xkyG^M9jPy;DQ7eif3wZ$U~k)y4rN3jo`^@C8mh;1utshP~9%jw4sjK!-PU z9e?DK=r5yah}={YfTR$RnVBi7%?qb_-GVZsb3EXfKz;jpYW*`fDAUlc{BUv)=syHt zGX}2iF`N1R$|G04BC%Jyz5Kd!p|kf@*w?x|q9%2u5)mPwpr8P^Gocn60?E3cI*DbU zeh4)HrNzTXbRPm74hO7SwTfEoyhoC*`gXoot|^Vp)cZJ?RqZBLirJ*|5U_07Qb5EVvz^aV^I$kQ6D0<3LQ4cVA z@?=qMUQeHV2RwqbX;`JOn|t>FR#M?;AOX<{AflK6u+0nK-Gk*H4>;*^;8Gp6^hYQU zfIq1QVL;NzlVTxY-n@B`mNu4JZ20^>_;2#<*E696fS?yp8Ff5BPEIb?KTKQ0V4GK2 zSt+&H+17Xv8g5*qmOcrU1R@$#0;+`o=Jy1xT=@_T9LTpwqP?pFHk{^vhmP7rg~$OA zoBCq`s)Yci_&;jYD0tw32dKr)hSMKFy9#aDQn7^z0e#s6FbP0Rn^(j2X4v+14Yl-5 z*bvZ%lfbYE;IwJeVCvMV)MDo;lK6>jyi}X0a2`N?0qDa6*ladYZC?9ZkHg`%dTQyP za8-b-2)21u zbGLcjWSoRuI@DxZawe?Je2`kY|Ev_cpZ#XZBU z8qpRzl&8;uCnh}Rs9 zN??l;(X_eSVawS)aN^q6x)}?Iu|&aLNn>Enuxv<@C{SD@aJc0gIC8liuDLyO_c!{R zJKV`n&loRyC5mVPl$Di<%KQyv1;@^DcQDc49G(!+LeG$xlmttb@V9vx$WDiphmY_G z;4nQy0g3&*ZC(b7ASnSvcm#m2v<$3^nmBPHo(!}{QUZuD3!t{P28Zl6;B{hRBAyJi zM^z7i;6A~PZEdPAe>UhD^*jJVya2wRcHaI2`y*!nzi%Vi1Be=|nlWQ$`b#hU-^;0~ nsrq~UR{!D^QUyrZxEtW_r%bs;&;Z{U00000NkvXXu0mjfi0rae literal 0 HcmV?d00001 diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Properties/launchSettings.json b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Properties/launchSettings.json new file mode 100644 index 00000000..30b5e4c6 --- /dev/null +++ b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "DebugRoslynSourceGenerator": { + "commandName": "DebugRoslynComponent", + "targetProject": "../Lombiq.HelpfulLibraries.SourceGenerators.Sample/Lombiq.HelpfulLibraries.SourceGenerators.Sample.csproj" + } + } +} \ No newline at end of file diff --git a/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Readme.md b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Readme.md new file mode 100644 index 00000000..3bee310d --- /dev/null +++ b/Lombiq.HelpfulLibraries.SourceGenerators/Lombiq.HelpfulLibraries.SourceGenerators/Readme.md @@ -0,0 +1,51 @@ +# Lombiq.HelpfulLibraries.SourceGenerators +A collection of helpful source generators. +**When using one of the generators you must run a build before errors will go away** + +- [ConstantFromJsonGenerator.cs](ConstantFromJsonGenerator.cs): A source generator that creates a constant from a JSON file. + +### Lombiq.HelpfulLibraries.SourceGenerators.Sample +A project that references source generators. It is used to test and showcase the source generators. + +### Lombiq.HelpfulLibraries.SourceGenerators.Tests +Unit tests for source generators. + +## How To? +### How to use the `ConstantFromJsonGenerator`? + +1. Add a JSON file to your project. +2. Set the `Build Action` of the JSON file to `AdditionalFiles` for example: + ```xml + + + + ``` +3. Wherever you want to use the JSON file, make sure to use a `partial class` and add the `ConstantFromJsonGenerator` attribute to it. + Where the first parameter is the name of the constant and the second parameter is the path to the JSON file, the last parameter is the name or 'key' for the value we are looking for. + ```csharp + [ConstantFromJson("GulpVersion", "package.json", "gulp")] + public partial class YourClass + { + + } + ``` +4. Run a build and the constant will be generated . +5. Use the constant in your code, full example: + ```csharp + using System; + using Generators; + + namespace Lombiq.HelpfulLibraries.SourceGenerators.Sample; + + [ConstantFromJson("GulpUglifyVersion", "package.json", "gulp-uglify")] + [ConstantFromJson("GulpVersion", "package.json", "gulp")] + public partial class Examples + { + // Show usage of the generated constants + public void LogVersions() + { + Console.WriteLine(GulpUglifyVersion); + Console.WriteLine(GulpVersion); + } + } + ``` \ No newline at end of file diff --git a/Lombiq.HelpfulLibraries.sln b/Lombiq.HelpfulLibraries.sln index 0d83cdf7..da10c232 100644 --- a/Lombiq.HelpfulLibraries.sln +++ b/Lombiq.HelpfulLibraries.sln @@ -21,6 +21,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lombiq.HelpfulLibraries.Cli EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lombiq.HelpfulLibraries.Refit", "Lombiq.HelpfulLibraries.Refit\Lombiq.HelpfulLibraries.Refit.csproj", "{5DC1A3D5-0626-4258-95C6-7E5CA5495A80}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lombiq.HelpfulLibraries.SourceGenerators", "Lombiq.HelpfulLibraries.SourceGenerators\Lombiq.HelpfulLibraries.SourceGenerators\Lombiq.HelpfulLibraries.SourceGenerators.csproj", "{FB53DB02-5C86-44C9-A88E-294B95C1F979}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lombiq.HelpfulLibraries.SourceGenerators.Sample", "Lombiq.HelpfulLibraries.SourceGenerators\Lombiq.HelpfulLibraries.SourceGenerators.Sample\Lombiq.HelpfulLibraries.SourceGenerators.Sample.csproj", "{DD8D992A-43C8-4E9B-8A40-B57F0D5B4217}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +67,14 @@ Global {5DC1A3D5-0626-4258-95C6-7E5CA5495A80}.Debug|Any CPU.Build.0 = Debug|Any CPU {5DC1A3D5-0626-4258-95C6-7E5CA5495A80}.Release|Any CPU.ActiveCfg = Release|Any CPU {5DC1A3D5-0626-4258-95C6-7E5CA5495A80}.Release|Any CPU.Build.0 = Release|Any CPU + {FB53DB02-5C86-44C9-A88E-294B95C1F979}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB53DB02-5C86-44C9-A88E-294B95C1F979}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB53DB02-5C86-44C9-A88E-294B95C1F979}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB53DB02-5C86-44C9-A88E-294B95C1F979}.Release|Any CPU.Build.0 = Release|Any CPU + {DD8D992A-43C8-4E9B-8A40-B57F0D5B4217}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD8D992A-43C8-4E9B-8A40-B57F0D5B4217}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD8D992A-43C8-4E9B-8A40-B57F0D5B4217}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD8D992A-43C8-4E9B-8A40-B57F0D5B4217}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE