diff --git a/src/CompileTimeComputation/Tests/Constants.cs b/src/CompileTimeComputation/Tests/Constants.cs index 49f57a9..55abbeb 100644 --- a/src/CompileTimeComputation/Tests/Constants.cs +++ b/src/CompileTimeComputation/Tests/Constants.cs @@ -5,18 +5,18 @@ public static class Constants public const string Source = """ namespace Foo; - public partial class Bar - { - public static readonly MD5 MD5 = global::System.Security.Cryptography.MD5.Create(); - - public const string UriString = "https://dgmjr.io/codegeneration/compiletimecomputation/samples"; - - [method: CompileTimeComputation("GuidString")] - public static string MakeGuidString() => MD5.ComputeHash(UriString.ToUTF8Bytes()).ToHexString(); - } + public partial class Bar +{ + public static readonly MD5 MD5 = global::System.Security.Cryptography.MD5.Create(); + + public const string UriString = "https://dgmjr.io/codegeneration/compiletimecomputation/samples"; + + [method: CompileTimeComputation("GuidString")] + public static string MakeGuidString() => MD5.ComputeHash(UriString.ToUTF8Bytes()).ToHexString(); +} """; - public const string Bar = nameof(Bar); - public const string Foo = nameof(Foo); - public const string GuidString = nameof(GuidString); + public const string Bar = nameof(Bar); +public const string Foo = nameof(Foo); +public const string GuidString = nameof(GuidString); } diff --git a/src/CompileTimeComputation/Tests/Tests.cs b/src/CompileTimeComputation/Tests/Tests.cs index e553850..ded28e7 100644 --- a/src/CompileTimeComputation/Tests/Tests.cs +++ b/src/CompileTimeComputation/Tests/Tests.cs @@ -6,41 +6,41 @@ namespace Dgmjr.CodeGeneration.CompileTimeComputation.Tests; public class Tests(ITestOutputHelper output) : BaseTest(output) { public override CompileTimeComputationGenerator UnitUnderTest { get; } = - new CompileTimeComputationGenerator(); - - [Fact] - public void Can_Produce_Compile_Time_Computation() - { - Logger.LogSourceToBeCompiled(Constants.Source); - var (inputCompilation, outputCompilation, _) = UnitUnderTest.RunGenerators( - Constants.Source - ); - var inputErrors = inputCompilation.GetDiagnostics().Errors(); - var outputErrors = outputCompilation.GetDiagnostics().Errors(); - // errors.Any().Should().BeFalse("there were errors during compilation"); - Logger.LogInformation("Input Errors:"); - if (inputErrors.Any()) - { - foreach (var error in inputErrors) - { - Logger.LogDiagnosticError(error.Location, error.Id, error.Descriptor); - } - } - Logger.LogInformation("Output Errors:"); - if (outputErrors.Any()) - { - foreach (var error in outputErrors) - { - Logger.LogDiagnosticError(error); - } - } - var compiledType = outputCompilation.GetTypeByMetadataName( - $"{Constants.Foo}.{Constants.Bar}" - ); - compiledType.Should().NotBeNull(); - var guidStringField = - compiledType.GetMembers(Constants.GuidString).FirstOrDefault() as IFieldSymbol; - guidStringField.Should().NotBeNull(); - Logger.LogInformation($"guidStringField.ConstantValue: {guidStringField.ConstantValue}"); - } + new CompileTimeComputationGenerator(); + +[Fact] +public void Can_Produce_Compile_Time_Computation() +{ + Logger.LogSourceToBeCompiled(Constants.Source); + var (inputCompilation, outputCompilation, _) = UnitUnderTest.RunGenerators( + Constants.Source + ); + var inputErrors = inputCompilation.GetDiagnostics().Errors(); + var outputErrors = outputCompilation.GetDiagnostics().Errors(); + // errors.Any().Should().BeFalse("there were errors during compilation"); + Logger.LogInformation("Input Errors:"); + if (inputErrors.Any()) + { + foreach (var error in inputErrors) + { + Logger.LogDiagnosticError(error.Location, error.Id, error.Descriptor); + } + } + Logger.LogInformation("Output Errors:"); + if (outputErrors.Any()) + { + foreach (var error in outputErrors) + { + Logger.LogDiagnosticError(error); + } + } + var compiledType = outputCompilation.GetTypeByMetadataName( + $"{Constants.Foo}.{Constants.Bar}" + ); + compiledType.Should().NotBeNull(); + var guidStringField = + compiledType.GetMembers(Constants.GuidString).FirstOrDefault() as IFieldSymbol; + guidStringField.Should().NotBeNull(); + Logger.LogInformation($"guidStringField.ConstantValue: {guidStringField.ConstantValue}"); +} } diff --git a/src/CompileTimeComputation/src/CompileTimeComputationGenerator.cs b/src/CompileTimeComputation/src/CompileTimeComputationGenerator.cs index f97b8d5..b025697 100644 --- a/src/CompileTimeComputation/src/CompileTimeComputationGenerator.cs +++ b/src/CompileTimeComputation/src/CompileTimeComputationGenerator.cs @@ -44,7 +44,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private static Action< Action - > RegisterPostInitializationOutput { get; set; } + > RegisterPostInitializationOutput + { get; set; } private static bool Select(SyntaxNode node, CancellationToken cancellationToken) => true; /*node is PropertyDeclarationSyntax pds ? pds.DescendantNodesAndTokensAndSelf(node => node.IsKind(SyntaxKind.PublicKeyword)).Any() && pds.DescendantNodesAndTokensAndSelf(node => node.IsKind(SyntaxKind.StaticKeyword)).Any() : node is MethodDeclarationSyntax mds && mds.DescendantNodesAndTokensAndSelf(node => node.IsKind(SyntaxKind.PublicKeyword)).Any() && mds.DescendantNodesAndTokensAndSelf(node => node.IsKind(SyntaxKind.StaticKeyword)).Any();*/ @@ -149,38 +150,47 @@ public static void Execute( var constDeclaration = HeaderTemplate.Render(new { Filename = filename }) + $$$""" - namespace {{{fieldSymbol.ContainingType.ContainingNamespace.ToDisplayString()}}}; - - {{{fieldSymbol.ContainingType.DeclaredAccessibility.ToString().ToLower().Replace("or", " ").Replace("and", " ")}}} {{{(fieldSymbol.ContainingType.IsStatic ? "static" : "")}}} partial {{{(fieldSymbol.ContainingType.IsRecord ? "record" : "")}}} {{{(fieldSymbol.ContainingType.TypeKind == TypeKind.Class ? "class" : fieldSymbol.ContainingType.TypeKind == TypeKind.Struct ? "struct" : $"#error Wrong data structure type: {fieldSymbol.ContainingType.TypeKind}")}}} {{{fieldSymbol.ContainingType.Name}}} - { - public const {{{fieldSymbol.ToDisplayString()}}} {{{name}}} = {{{(fieldType.Name.Equals(nameof(String), InvariantCultureIgnoreCase) ? "\"" : "")}}}{{{funcResult}}}{{{(fieldSymbol.Name.Equals(nameof(String), InvariantCultureIgnoreCase) ? "\"" : "")}}}; + namespace {{{fieldSymbol.ContainingType.ContainingNamespace.ToDisplayString() } +}}; + +{ { { fieldSymbol.ContainingType.DeclaredAccessibility.ToString().ToLower().Replace("or", " ").Replace("and", " ")} } } +{ { { (fieldSymbol.ContainingType.IsStatic ? "static" : "")} } } +partial +{ { { (fieldSymbol.ContainingType.IsRecord ? "record" : "")} } } +{ { { (fieldSymbol.ContainingType.TypeKind == TypeKind.Class ? "class" : fieldSymbol.ContainingType.TypeKind == TypeKind.Struct ? "struct" : $"#error Wrong data structure type: {fieldSymbol.ContainingType.TypeKind}")} } } +{ { { fieldSymbol.ContainingType.Name} } } +{ + public const { { { fieldSymbol.ToDisplayString()} } } +{ { { name} } } = { { { (fieldType.Name.Equals(nameof(String), InvariantCultureIgnoreCase) ? "\"" : "")} } } +{ { { funcResult} } } +{ { { (fieldSymbol.Name.Equals(nameof(String), InvariantCultureIgnoreCase) ? "\"" : "")} } }; } """; - - // Add the class and const variable declarations to the compilation - context.AddSource(filename, constDeclaration); - } - catch (TargetInvocationException tiex) - { - throw tiex.InnerException ?? tiex; + + // Add the class and const variable declarations to the compilation +context.AddSource(filename, constDeclaration); } + catch (TargetInvocationException tiex) +{ + throw tiex.InnerException ?? tiex; +} } - else - { - context.ReportDiagnostic( - Diagnostic.Create( - new DiagnosticDescriptor( - "CTCG002", - "Error generatimg compile-time computed constant: must be const-able", - Format(CTCG002ErrorMessage, fieldSymbolDisplay), - "CTCG002: Field must be const-able", - DiagnosticSeverity.Error, - true - ), - fieldSymbol.Locations.FirstOrDefault() - ) - ); - } + else +{ + context.ReportDiagnostic( + Diagnostic.Create( + new DiagnosticDescriptor( + "CTCG002", + "Error generatimg compile-time computed constant: must be const-able", + Format(CTCG002ErrorMessage, fieldSymbolDisplay), + "CTCG002: Field must be const-able", + DiagnosticSeverity.Error, + true + ), + fieldSymbol.Locations.FirstOrDefault() + ) + ); +} } } } diff --git a/src/CompileTimeComputation/src/Constants.cs b/src/CompileTimeComputation/src/Constants.cs index 0a0187d..8400e06 100644 --- a/src/CompileTimeComputation/src/Constants.cs +++ b/src/CompileTimeComputation/src/Constants.cs @@ -84,16 +84,19 @@ public static class Constants ); public const string CompileTimeComputationClassDeclaration = $$$""" - {{{GeneratedCodeAttributes}}} - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] - internal sealed class {{{CompileTimeComputation}}} : Attribute - { - public {{{CompileTimeComputation}}}(string name) - { - Name = name; - } - - public string Name { get; } + {{{GeneratedCodeAttributes +} +}} + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)] +internal sealed class {{ { CompileTimeComputation} }} : Attribute +{ + public +{ { { CompileTimeComputation} } } (string name) + { + Name = name; +} + +public string Name { get; } } """; } diff --git a/src/Constants/Constants.cs b/src/Constants/Constants.cs index bae12f9..69f363b 100644 --- a/src/Constants/Constants.cs +++ b/src/Constants/Constants.cs @@ -82,7 +82,7 @@ public static partial class Constants ); public const string AttributeDeclarationTemplateString = """" #nullable enable - + [AttributeUsage({{ regex.replace(regex.replace attribute_targets "(?:(?:^)|(?: ))" "System.AttributeTargets.") "," " | " } }, AllowMultiple = false)] internal class {{ attribute_class_name }} : { { attribute_base_type.full_name } } @@ -106,17 +106,17 @@ internal class {{ attribute_class_name }} : { { attribute_base_type.full_name } { get; set; } { { ~end ~} } } -""""; - public static readonly Scriban.Template AttributeDeclarationTemplate = Scriban.Template.Parse( - AttributeDeclarationTemplateString - ); - - public static readonly string ToolName = typeof(Constants).Assembly.GetName().Name; - public static readonly string ToolVersion = typeof(Constants).Assembly - .GetName() - .GetName() - .Version.ToString(); - public static readonly string MinimalCodeHeaderTemplateString = $$$"""" +""""; +public static readonly Scriban.Template AttributeDeclarationTemplate = Scriban.Template.Parse( + AttributeDeclarationTemplateString +); + +public static readonly string ToolName = typeof(Constants).Assembly.GetName().Name; +public static readonly string ToolVersion = typeof(Constants).Assembly + .GetName() + .GetName() + .Version.ToString(); +public static readonly string MinimalCodeHeaderTemplateString = $$$"""" /* * * This code was generated by the{{{ToolName}}}, version {{{ToolVersion}}} @@ -128,64 +128,64 @@ internal class {{ attribute_class_name }} : { { attribute_base_type.full_name } * Generated: {{ timestamp }} * Generated by: {{{System.Environment.UserName}}} */ -""""; - public static readonly Scriban.Template MinimalCodeHeaderTemplate = Scriban.Template.Parse( - MinimalCodeHeaderTemplateString - ); - - public static string GenerateMininmalCodeHeader(string filename, string timestamp) - { - return MinimalCodeHeaderTemplate.Render(new { filename, timestamp }); - } - - public static string GenerateAttributeDeclaration( - string attributeName, - AttributeTargets attributeTargets = AttributeTargets.All, - Type baseType = default, - params AttributeProperty[] properties - ) - { - baseType ??= typeof(Attribute); - var result = - MinimalCodeHeaderTemplate.Render( - new - { - filename = $"{attributeName}.cs", - timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm") - } - ) - + Environment.NewLine - + AttributeDeclarationTemplate.Render( - new AttributeInfo(attributeName, baseType, attributeTargets.ToString(), properties) - ); - - return result; - } - - public static string GenerateAttributeDeclaration(AttributeInfo attributeInfo) - { - return MinimalCodeHeaderTemplate.Render(attributeInfo); - } - - public static string TrimToSentinel(this string codeHeader) - { - var sentinelIndex = codeHeader.IndexOf(BeginCodeHeaderSentinel); - if (sentinelIndex == -1) - { - return codeHeader; - } - return codeHeader.Substring(sentinelIndex + BeginCodeHeaderSentinel.Length).Trim(); - } - - public static IEnumerable<(string Name, string? Email)> ParseAuthors(string authors) - { - var authorsList = authors.Split(';'); - foreach (var author in authorsList) - { - var authorParts = author.Split('<'); - var authorName = authorParts.First().Trim(); - var authorEmail = authorParts.Skip(1).FirstOrDefault()?.Trim().TrimEnd('>'); - yield return (authorName, authorEmail); - } - } +""""; +public static readonly Scriban.Template MinimalCodeHeaderTemplate = Scriban.Template.Parse( + MinimalCodeHeaderTemplateString +); + +public static string GenerateMininmalCodeHeader(string filename, string timestamp) +{ + return MinimalCodeHeaderTemplate.Render(new { filename, timestamp }); +} + +public static string GenerateAttributeDeclaration( + string attributeName, + AttributeTargets attributeTargets = AttributeTargets.All, + Type baseType = default, + params AttributeProperty[] properties +) +{ + baseType ??= typeof(Attribute); + var result = + MinimalCodeHeaderTemplate.Render( + new + { + filename = $"{attributeName}.cs", + timestamp = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm") + } + ) + + Environment.NewLine + + AttributeDeclarationTemplate.Render( + new AttributeInfo(attributeName, baseType, attributeTargets.ToString(), properties) + ); + + return result; +} + +public static string GenerateAttributeDeclaration(AttributeInfo attributeInfo) +{ + return MinimalCodeHeaderTemplate.Render(attributeInfo); +} + +public static string TrimToSentinel(this string codeHeader) +{ + var sentinelIndex = codeHeader.IndexOf(BeginCodeHeaderSentinel); + if (sentinelIndex == -1) + { + return codeHeader; + } + return codeHeader.Substring(sentinelIndex + BeginCodeHeaderSentinel.Length).Trim(); +} + +public static IEnumerable<(string Name, string? Email)> ParseAuthors(string authors) +{ + var authorsList = authors.Split(';'); + foreach (var author in authorsList) + { + var authorParts = author.Split('<'); + var authorName = authorParts.First().Trim(); + var authorEmail = authorParts.Skip(1).FirstOrDefault()?.Trim().TrimEnd('>'); + yield return (authorName, authorEmail); + } +} } diff --git a/src/Extensions/MemberSymbolExtensions/MemberSymbolExtenisons.cs b/src/Extensions/MemberSymbolExtensions/MemberSymbolExtenisons.cs index d120461..1401d79 100644 --- a/src/Extensions/MemberSymbolExtensions/MemberSymbolExtenisons.cs +++ b/src/Extensions/MemberSymbolExtensions/MemberSymbolExtenisons.cs @@ -41,36 +41,38 @@ params object[] arguments var program = $$""" using System; - public static class {{programClassName}} - { - public static object Run() => {{methodSymbol.ContainingType.ToDisplayString()}}.{{methodSymbol.Name}}({{Join(", ", arguments.Select(arg => $"{(arg is string ? "\"" : "")}{arg}{(arg is string ? "\"" : "")}"))}}); - } - """; + public static class {{programClassName +} +} +{ + public static object Run() => { { methodSymbol.ContainingType.ToDisplayString()} }.{ { methodSymbol.Name} } ({ { Join(", ", arguments.Select(arg => $"{(arg is string ? "\"" : "")}{arg}{(arg is string ? "\"" : "")}"))} }); +} +"""; return AddToCompilatonAndCallRun(compilation, program, programClassName); } public static object GetStaticPropertyValue( this IPropertySymbol propertySymbol, Compilation compilation - ) - { - if ( - !propertySymbol.IsStatic || propertySymbol.DeclaredAccessibility != Accessibility.Public - ) - { - throw new InvalidOperationException( - $"The property {propertySymbol.ToDisplayString()} must be public and static." - ); - } - var programClassName = $"Program_{guid.NewGuid().ToByteArray().ToHexString()}"; - var program = $$""" + ) +{ + if ( + !propertySymbol.IsStatic || propertySymbol.DeclaredAccessibility != Accessibility.Public + ) + { + throw new InvalidOperationException( + $"The property {propertySymbol.ToDisplayString()} must be public and static." + ); + } + var programClassName = $"Program_{guid.NewGuid().ToByteArray().ToHexString()}"; + var program = $$""" using System; - public static class {{programClassName}} - { - public static object Run() => {{propertySymbol.ContainingType.ToDisplayString()}}.{{propertySymbol.Name}}; - } - """; + public static class {{ programClassName}} + { + public static object Run() => { { propertySymbol.ContainingType.ToDisplayString()} }.{ { propertySymbol.Name} }; +} +"""; return AddToCompilatonAndCallRun(compilation, program, programClassName); } @@ -78,38 +80,38 @@ private static object AddToCompilatonAndCallRun( Compilation compilation, string cSharpCode, string programClassName - ) - { - compilation = compilation.AddSyntaxTrees( - SyntaxFactory.ParseSyntaxTree(cSharpCode, compilation.SyntaxTrees.First().Options) - ); - return EmitAndCallRun(compilation, programClassName, cSharpCode); - } - - private static object EmitAndCallRun( - Compilation compilation, - string programClassName, - string? extraInfo = "" - ) - { - using var asmStream = new MemoryStream(); - var emitResult = compilation.Emit( - asmStream, - options: new EmitOptions(false, debugInformationFormat: DebugInformationFormat.Embedded) - ); - if (!emitResult.Success) - { - var errorDiagnostic = emitResult.Diagnostics.FirstOrDefault( - diag => diag.Severity == DiagnosticSeverity.Error - ); - throw new CompilationException( - $"{programClassName}: {errorDiagnostic?.GetMessage()}{(!IsNullOrEmpty(extraInfo) ? ", *" : "")}{extraInfo?.Replace("\n", " ").Replace("\r", " ")}{(!IsNullOrEmpty(extraInfo) ? "*" : "")}" - ); - } - asmStream.Flush(); - var asm = Assembly.Load(asmStream.GetBuffer()); - var programClass = Find(asm.GetExportedTypes(), t => t.Name == programClassName); - var runMethod = programClass.GetMethod("Run"); - return runMethod.Invoke(null, null); - } + ) +{ + compilation = compilation.AddSyntaxTrees( + SyntaxFactory.ParseSyntaxTree(cSharpCode, compilation.SyntaxTrees.First().Options) + ); + return EmitAndCallRun(compilation, programClassName, cSharpCode); +} + +private static object EmitAndCallRun( + Compilation compilation, + string programClassName, + string? extraInfo = "" +) +{ + using var asmStream = new MemoryStream(); + var emitResult = compilation.Emit( + asmStream, + options: new EmitOptions(false, debugInformationFormat: DebugInformationFormat.Embedded) + ); + if (!emitResult.Success) + { + var errorDiagnostic = emitResult.Diagnostics.FirstOrDefault( + diag => diag.Severity == DiagnosticSeverity.Error + ); + throw new CompilationException( + $"{programClassName}: {errorDiagnostic?.GetMessage()}{(!IsNullOrEmpty(extraInfo) ? ", *" : "")}{extraInfo?.Replace("\n", " ").Replace("\r", " ")}{(!IsNullOrEmpty(extraInfo) ? "*" : "")}" + ); + } + asmStream.Flush(); + var asm = Assembly.Load(asmStream.GetBuffer()); + var programClass = Find(asm.GetExportedTypes(), t => t.Name == programClassName); + var runMethod = programClass.GetMethod("Run"); + return runMethod.Invoke(null, null); +} } diff --git a/src/InjectedAttribute/InjectedAttributeCodeGenerator.cs b/src/InjectedAttribute/InjectedAttributeCodeGenerator.cs index edd5462..5fa5ed8 100644 --- a/src/InjectedAttribute/InjectedAttributeCodeGenerator.cs +++ b/src/InjectedAttribute/InjectedAttributeCodeGenerator.cs @@ -25,7 +25,7 @@ public class InjectedConstructorGenerator : IIncrementalGenerator private const string AttributeDeclaration = @$" [System.AttributeUsage(System.AttributeTargets.Property)] - [System.CodeDom.Compiler.GeneratedCode(""InjectedConstructorGenerator"", ""{ThisAssembly .Info .Version}"")] + [System.CodeDom.Compiler.GeneratedCode(""InjectedConstructorGenerator"", ""{ThisAssembly.Info.Version}"")] public class InjectedAttribute : System.Attribute {{ }} "; public void Initialize(IncrementalGeneratorInitializationContext context)