From dc79a4b5b7505c56a41ae1613dafe61a204cdcfe Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 13 Apr 2022 15:30:09 +0200 Subject: [PATCH 01/21] Moved project to C# 10 and changed all namespace to simplified syntax. --- .../AutoNotifyGenerator.cs | 185 +++-- .../CSharpSourceGeneratorSamples.csproj | 4 +- .../SourceGeneratorSamples/CsvGenerator.cs | 241 ++++--- .../HelloWorldGenerator.cs | 45 +- .../SourceGeneratorSamples/MathsGenerator.cs | 680 +++++++++--------- .../MustacheGenerator.cs | 81 ++- .../SettingsXmlGenerator.cs | 85 ++- 7 files changed, 658 insertions(+), 663 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index 2f869f2c2..539245420 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -7,12 +7,12 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; -namespace SourceGeneratorSamples +namespace SourceGeneratorSamples; + +[Generator] +public class AutoNotifyGenerator : ISourceGenerator { - [Generator] - public class AutoNotifyGenerator : ISourceGenerator - { - private const string attributeText = @" + private const string attributeText = @" using System; namespace AutoNotify { @@ -29,84 +29,84 @@ public AutoNotifyAttribute() "; - public void Initialize(GeneratorInitializationContext context) - { - // Register the attribute source - context.RegisterForPostInitialization((i) => i.AddSource("AutoNotifyAttribute", attributeText)); + public void Initialize(GeneratorInitializationContext context) + { + // Register the attribute source + context.RegisterForPostInitialization((i) => i.AddSource("AutoNotifyAttribute", attributeText)); - // Register a syntax receiver that will be created for each generation pass - context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); - } + // Register a syntax receiver that will be created for each generation pass + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } - public void Execute(GeneratorExecutionContext context) - { - // retrieve the populated receiver - if (!(context.SyntaxContextReceiver is SyntaxReceiver receiver)) - return; + public void Execute(GeneratorExecutionContext context) + { + // retrieve the populated receiver + if (!(context.SyntaxContextReceiver is SyntaxReceiver receiver)) + return; - // get the added attribute, and INotifyPropertyChanged - INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute"); - INamedTypeSymbol notifySymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); + // get the added attribute, and INotifyPropertyChanged + INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute"); + INamedTypeSymbol notifySymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); - // group the fields by class, and generate the source - foreach (IGrouping group in receiver.Fields.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) - { - string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context); - context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); - } + // group the fields by class, and generate the source + foreach (IGrouping group in receiver.Fields.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) + { + string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context); + context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); } + } - private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol, GeneratorExecutionContext context) + private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol, GeneratorExecutionContext context) + { + if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) { - if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) - { - return null; //TODO: issue a diagnostic that it must be top level - } + return null; //TODO: issue a diagnostic that it must be top level + } - string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); + string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); - // begin building the generated source - StringBuilder source = new StringBuilder($@" + // begin building the generated source + StringBuilder source = new StringBuilder($@" namespace {namespaceName} {{ public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()} {{ "); - // if the class doesn't implement INotifyPropertyChanged already, add it - if (!classSymbol.Interfaces.Contains(notifySymbol, SymbolEqualityComparer.Default)) - { - source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); - } - - // create properties for each field - foreach (IFieldSymbol fieldSymbol in fields) - { - ProcessField(source, fieldSymbol, attributeSymbol); - } - - source.Append("} }"); - return source.ToString(); + // if the class doesn't implement INotifyPropertyChanged already, add it + if (!classSymbol.Interfaces.Contains(notifySymbol, SymbolEqualityComparer.Default)) + { + source.Append("public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;"); } - private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol) + // create properties for each field + foreach (IFieldSymbol fieldSymbol in fields) { - // get the name and type of the field - string fieldName = fieldSymbol.Name; - ITypeSymbol fieldType = fieldSymbol.Type; + ProcessField(source, fieldSymbol, attributeSymbol); + } - // get the AutoNotify attribute from the field, and any associated data - AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); - TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "PropertyName").Value; + source.Append("} }"); + return source.ToString(); + } - string propertyName = chooseName(fieldName, overridenNameOpt); - if (propertyName.Length == 0 || propertyName == fieldName) - { - //TODO: issue a diagnostic that we can't process this field - return; - } + private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbol attributeSymbol) + { + // get the name and type of the field + string fieldName = fieldSymbol.Name; + ITypeSymbol fieldType = fieldSymbol.Type; - source.Append($@" + // get the AutoNotify attribute from the field, and any associated data + AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); + TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "PropertyName").Value; + + string propertyName = chooseName(fieldName, overridenNameOpt); + if (propertyName.Length == 0 || propertyName == fieldName) + { + //TODO: issue a diagnostic that we can't process this field + return; + } + + source.Append($@" public {fieldType} {propertyName} {{ get @@ -123,49 +123,48 @@ private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbo "); - string chooseName(string fieldName, TypedConstant overridenNameOpt) + string chooseName(string fieldName, TypedConstant overridenNameOpt) + { + if (!overridenNameOpt.IsNull) { - if (!overridenNameOpt.IsNull) - { - return overridenNameOpt.Value.ToString(); - } - - fieldName = fieldName.TrimStart('_'); - if (fieldName.Length == 0) - return string.Empty; + return overridenNameOpt.Value.ToString(); + } - if (fieldName.Length == 1) - return fieldName.ToUpper(); + fieldName = fieldName.TrimStart('_'); + if (fieldName.Length == 0) + return string.Empty; - return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1); - } + if (fieldName.Length == 1) + return fieldName.ToUpper(); + return fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1); } + } + + /// + /// Created on demand before each generation pass + /// + class SyntaxReceiver : ISyntaxContextReceiver + { + public List Fields { get; } = new List(); + /// - /// Created on demand before each generation pass + /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation /// - class SyntaxReceiver : ISyntaxContextReceiver + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { - public List Fields { get; } = new List(); - - /// - /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation - /// - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + // any field with at least one attribute is a candidate for property generation + if (context.Node is FieldDeclarationSyntax fieldDeclarationSyntax + && fieldDeclarationSyntax.AttributeLists.Count > 0) { - // any field with at least one attribute is a candidate for property generation - if (context.Node is FieldDeclarationSyntax fieldDeclarationSyntax - && fieldDeclarationSyntax.AttributeLists.Count > 0) + foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables) { - foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables) + // Get the symbol being declared by the field, and keep it if its annotated + IFieldSymbol fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable) as IFieldSymbol; + if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.ToDisplayString() == "AutoNotify.AutoNotifyAttribute")) { - // Get the symbol being declared by the field, and keep it if its annotated - IFieldSymbol fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable) as IFieldSymbol; - if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.ToDisplayString() == "AutoNotify.AutoNotifyAttribute")) - { - Fields.Add(fieldSymbol); - } + Fields.Add(fieldSymbol); } } } diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj index 47c428cf5..b10063ca7 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj @@ -2,12 +2,12 @@ netstandard2.0 - 8.0 + 10.0 - + diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs index d25b5111e..bebfecbaf 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs @@ -11,175 +11,174 @@ // CsvTextFileParser from https://github.com/22222/CsvTextFieldParser adding suppression rules for default VS config -namespace CsvGenerator +namespace CsvGenerator; + +[Generator] +public class CSVGenerator : ISourceGenerator { - [Generator] - public class CSVGenerator : ISourceGenerator + public enum CsvLoadType { - public enum CsvLoadType - { - Startup, - OnDemand - } + Startup, + OnDemand + } + + // Guesses type of property for the object from the value of a csv field + public static string GetCsvFieldType(string exemplar) => exemplar switch + { + _ when bool.TryParse(exemplar, out _) => "bool", + _ when int.TryParse(exemplar, out _) => "int", + _ when double.TryParse(exemplar, out _) => "double", + _ => "string" + }; + + // Examines the header row and the first row in the csv file to gather all header types and names + // Also it returns the first row of data, because it must be read to figure out the types, + // As the CsvTextFieldParser cannot 'Peek' ahead of one line. If there is no first line, + // it consider all properties as strings. The generator returns an empty list of properly + // typed objects in such cas. If the file is completely empty, an error is generated. + public static (string[], string[], string[]?) ExtractProperties(CsvTextFieldParser parser) + { + string[]? headerFields = parser.ReadFields(); + if (headerFields == null) throw new Exception("Empty csv file!"); - // Guesses type of property for the object from the value of a csv field - public static string GetCsvFieldType(string exemplar) => exemplar switch + string[]? firstLineFields = parser.ReadFields(); + if (firstLineFields == null) { - _ when bool.TryParse(exemplar, out _) => "bool", - _ when int.TryParse(exemplar, out _) => "int", - _ when double.TryParse(exemplar, out _) => "double", - _ => "string" - }; - - // Examines the header row and the first row in the csv file to gather all header types and names - // Also it returns the first row of data, because it must be read to figure out the types, - // As the CsvTextFieldParser cannot 'Peek' ahead of one line. If there is no first line, - // it consider all properties as strings. The generator returns an empty list of properly - // typed objects in such cas. If the file is completely empty, an error is generated. - public static (string[], string[], string[]?) ExtractProperties(CsvTextFieldParser parser) + return (Enumerable.Repeat("string", headerFields.Length).ToArray(), headerFields, firstLineFields); + } + else { - string[]? headerFields = parser.ReadFields(); - if (headerFields == null) throw new Exception("Empty csv file!"); - - string[]? firstLineFields = parser.ReadFields(); - if (firstLineFields == null) - { - return (Enumerable.Repeat("string", headerFields.Length).ToArray(), headerFields, firstLineFields); - } - else - { - return (firstLineFields.Select(GetCsvFieldType).ToArray(), headerFields.Select(StringToValidPropertyName).ToArray(), firstLineFields); - } + return (firstLineFields.Select(GetCsvFieldType).ToArray(), headerFields.Select(StringToValidPropertyName).ToArray(), firstLineFields); } + } - // Adds a class to the `CSV` namespace for each `csv` file passed in. The class has a static property - // named `All` that returns the list of strongly typed objects generated on demand at first access. - // There is the slight chance of a race condition in a multi-thread program, but the result is relatively benign - // , loading the collection multiple times instead of once. Measures could be taken to avoid that. - public static string GenerateClassFile(string className, string csvText, CsvLoadType loadTime, bool cacheObjects) - { - StringBuilder sb = new StringBuilder(); - using CsvTextFieldParser parser = new CsvTextFieldParser(new StringReader(csvText)); + // Adds a class to the `CSV` namespace for each `csv` file passed in. The class has a static property + // named `All` that returns the list of strongly typed objects generated on demand at first access. + // There is the slight chance of a race condition in a multi-thread program, but the result is relatively benign + // , loading the collection multiple times instead of once. Measures could be taken to avoid that. + public static string GenerateClassFile(string className, string csvText, CsvLoadType loadTime, bool cacheObjects) + { + StringBuilder sb = new StringBuilder(); + using CsvTextFieldParser parser = new CsvTextFieldParser(new StringReader(csvText)); - //// Usings - sb.Append(@" + //// Usings + sb.Append(@" #nullable enable namespace CSV { using System.Collections.Generic; "); - //// Class Definition - sb.Append($" public class {className} {{\n"); + //// Class Definition + sb.Append($" public class {className} {{\n"); - if (loadTime == CsvLoadType.Startup) - { - sb.Append(@$" + if (loadTime == CsvLoadType.Startup) + { + sb.Append(@$" static {className}() {{ var x = All; }} "); - } - (string[] types, string[] names, string[]? fields) = ExtractProperties(parser); - int minLen = Math.Min(types.Length, names.Length); + } + (string[] types, string[] names, string[]? fields) = ExtractProperties(parser); + int minLen = Math.Min(types.Length, names.Length); - for (int i = 0; i < minLen; i++) - { - sb.AppendLine($" public {types[i]} {StringToValidPropertyName(names[i])} {{ get; set;}} = default!;"); - } - sb.Append("\n"); + for (int i = 0; i < minLen; i++) + { + sb.AppendLine($" public {types[i]} {StringToValidPropertyName(names[i])} {{ get; set;}} = default!;"); + } + sb.Append("\n"); - //// Loading data - sb.AppendLine($" static IEnumerable<{className}>? _all = null;"); - sb.Append($@" + //// Loading data + sb.AppendLine($" static IEnumerable<{className}>? _all = null;"); + sb.Append($@" public static IEnumerable<{className}> All {{ get {{"); - if (cacheObjects) sb.Append(@" + if (cacheObjects) sb.Append(@" if(_all != null) return _all; "); - sb.Append(@$" + sb.Append(@$" List<{className}> l = new List<{className}>(); {className} c; "); - // This awkwardness comes from having to pre-read one row to figure out the types of props. - do - { - if (fields == null) continue; - if (fields.Length < minLen) throw new Exception("Not enough fields in CSV file."); + // This awkwardness comes from having to pre-read one row to figure out the types of props. + do + { + if (fields == null) continue; + if (fields.Length < minLen) throw new Exception("Not enough fields in CSV file."); - sb.AppendLine($" c = new {className}();"); - string value = ""; - for (int i = 0; i < minLen; i++) - { - // Wrap strings in quotes. - value = GetCsvFieldType(fields[i]) == "string" ? $"\"{fields[i].Trim().Trim(new char[] { '"' })}\"" : fields[i]; - sb.AppendLine($" c.{names[i]} = {value};"); - } - sb.AppendLine(" l.Add(c);"); + sb.AppendLine($" c = new {className}();"); + string value = ""; + for (int i = 0; i < minLen; i++) + { + // Wrap strings in quotes. + value = GetCsvFieldType(fields[i]) == "string" ? $"\"{fields[i].Trim().Trim(new char[] { '"' })}\"" : fields[i]; + sb.AppendLine($" c.{names[i]} = {value};"); + } + sb.AppendLine(" l.Add(c);"); - fields = parser.ReadFields(); - } while (!(fields == null)); + fields = parser.ReadFields(); + } while (!(fields == null)); - sb.AppendLine(" _all = l;"); - sb.AppendLine(" return l;"); + sb.AppendLine(" _all = l;"); + sb.AppendLine(" return l;"); - // Close things (property, class, namespace) - sb.Append(" }\n }\n }\n}\n"); - return sb.ToString(); + // Close things (property, class, namespace) + sb.Append(" }\n }\n }\n}\n"); + return sb.ToString(); - } + } - static string StringToValidPropertyName(string s) - { - s = s.Trim(); - s = char.IsLetter(s[0]) ? char.ToUpper(s[0]) + s.Substring(1) : s; - s = char.IsDigit(s.Trim()[0]) ? "_" + s : s; - s = new string(s.Select(ch => char.IsDigit(ch) || char.IsLetter(ch) ? ch : '_').ToArray()); - return s; - } + static string StringToValidPropertyName(string s) + { + s = s.Trim(); + s = char.IsLetter(s[0]) ? char.ToUpper(s[0]) + s.Substring(1) : s; + s = char.IsDigit(s.Trim()[0]) ? "_" + s : s; + s = new string(s.Select(ch => char.IsDigit(ch) || char.IsLetter(ch) ? ch : '_').ToArray()); + return s; + } - static IEnumerable<(string, string)> SourceFilesFromAdditionalFile(CsvLoadType loadTime, bool cacheObjects, AdditionalText file) - { - string className = Path.GetFileNameWithoutExtension(file.Path); - string csvText = file.GetText()!.ToString(); - return new (string, string)[] { (className, GenerateClassFile(className, csvText, loadTime, cacheObjects)) }; - } + static IEnumerable<(string, string)> SourceFilesFromAdditionalFile(CsvLoadType loadTime, bool cacheObjects, AdditionalText file) + { + string className = Path.GetFileNameWithoutExtension(file.Path); + string csvText = file.GetText()!.ToString(); + return new (string, string)[] { (className, GenerateClassFile(className, csvText, loadTime, cacheObjects)) }; + } - static IEnumerable<(string, string)> SourceFilesFromAdditionalFiles(IEnumerable<(CsvLoadType loadTime, bool cacheObjects, AdditionalText file)> pathsData) - => pathsData.SelectMany(d => SourceFilesFromAdditionalFile(d.loadTime, d.cacheObjects, d.file)); + static IEnumerable<(string, string)> SourceFilesFromAdditionalFiles(IEnumerable<(CsvLoadType loadTime, bool cacheObjects, AdditionalText file)> pathsData) + => pathsData.SelectMany(d => SourceFilesFromAdditionalFile(d.loadTime, d.cacheObjects, d.file)); - static IEnumerable<(CsvLoadType, bool, AdditionalText)> GetLoadOptions(GeneratorExecutionContext context) + static IEnumerable<(CsvLoadType, bool, AdditionalText)> GetLoadOptions(GeneratorExecutionContext context) + { + foreach (AdditionalText file in context.AdditionalFiles) { - foreach (AdditionalText file in context.AdditionalFiles) + if (Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase)) { - if (Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase)) - { - // are there any options for it? - context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", out string? loadTimeString); - Enum.TryParse(loadTimeString, ignoreCase: true, out CsvLoadType loadType); + // are there any options for it? + context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", out string? loadTimeString); + Enum.TryParse(loadTimeString, ignoreCase: true, out CsvLoadType loadType); - context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", out string? cacheObjectsString); - bool.TryParse(cacheObjectsString, out bool cacheObjects); + context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", out string? cacheObjectsString); + bool.TryParse(cacheObjectsString, out bool cacheObjects); - yield return (loadType, cacheObjects, file); - } + yield return (loadType, cacheObjects, file); } } + } - public void Execute(GeneratorExecutionContext context) - { - IEnumerable<(CsvLoadType, bool, AdditionalText)> options = GetLoadOptions(context); - IEnumerable<(string, string)> nameCodeSequence = SourceFilesFromAdditionalFiles(options); - foreach ((string name, string code) in nameCodeSequence) - context.AddSource($"Csv_{name}", SourceText.From(code, Encoding.UTF8)); - } + public void Execute(GeneratorExecutionContext context) + { + IEnumerable<(CsvLoadType, bool, AdditionalText)> options = GetLoadOptions(context); + IEnumerable<(string, string)> nameCodeSequence = SourceFilesFromAdditionalFiles(options); + foreach ((string name, string code) in nameCodeSequence) + context.AddSource($"Csv_{name}", SourceText.From(code, Encoding.UTF8)); + } - public void Initialize(GeneratorInitializationContext context) - { - } + public void Initialize(GeneratorInitializationContext context) + { } } #pragma warning restore IDE0008 // Use explicit type diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index d3f099527..cbf8d1a8a 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -4,15 +4,15 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; -namespace SourceGeneratorSamples +namespace SourceGeneratorSamples; + +[Generator] +public class HelloWorldGenerator : ISourceGenerator { - [Generator] - public class HelloWorldGenerator : ISourceGenerator + public void Execute(GeneratorExecutionContext context) { - public void Execute(GeneratorExecutionContext context) - { - // begin creating the source we'll inject into the users compilation - StringBuilder sourceBuilder = new StringBuilder(@" + // begin creating the source we'll inject into the users compilation + StringBuilder sourceBuilder = new StringBuilder(@" using System; namespace HelloWorldGenerated { @@ -24,28 +24,27 @@ public static void SayHello() Console.WriteLine(""The following syntax trees existed in the compilation that created this program:""); "); - // using the context, get a list of syntax trees in the users compilation - IEnumerable syntaxTrees = context.Compilation.SyntaxTrees; + // using the context, get a list of syntax trees in the users compilation + IEnumerable syntaxTrees = context.Compilation.SyntaxTrees; - // add the filepath of each tree to the class we're building - foreach (SyntaxTree tree in syntaxTrees) - { - sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");"); - } + // add the filepath of each tree to the class we're building + foreach (SyntaxTree tree in syntaxTrees) + { + sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");"); + } - // finish creating the source to inject - sourceBuilder.Append(@" + // finish creating the source to inject + sourceBuilder.Append(@" } } }"); - // inject the created source into the users compilation - context.AddSource("helloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); - } + // inject the created source into the users compilation + context.AddSource("helloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } - public void Initialize(GeneratorInitializationContext context) - { - // No initialization required - } + public void Initialize(GeneratorInitializationContext context) + { + // No initialization required } } diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs index 7cf2c1676..36603ed3f 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs @@ -13,140 +13,141 @@ #pragma warning disable IDE0008 // Use explicit type -namespace MathsGenerator { - public enum TokenType { - Number, - Identifier, - Operation, - OpenParens, - CloseParens, - Equal, - EOL, - EOF, - Spaces, - Comma, - Sum, - None - } +namespace MathsGenerator; + +public enum TokenType { + Number, + Identifier, + Operation, + OpenParens, + CloseParens, + Equal, + EOL, + EOF, + Spaces, + Comma, + Sum, + None +} - public struct Token { - public TokenType Type; - public string Value; - public int Line; - public int Column; - } +public struct Token { + public TokenType Type; + public string Value; + public int Line; + public int Column; +} - public static class Lexer { +public static class Lexer { - public static void PrintTokens(IEnumerable tokens) { - foreach (var token in tokens) { - WriteLine($"{token.Line}, {token.Column}, {token.Type}, {token.Value}"); - } + public static void PrintTokens(IEnumerable tokens) { + foreach (var token in tokens) { + WriteLine($"{token.Line}, {token.Column}, {token.Type}, {token.Value}"); } + } - static (TokenType, string)[] tokenStrings = { - (TokenType.EOL, @"(\r\n|\r|\n)"), - (TokenType.Spaces, @"\s+"), - (TokenType.Number, @"[+-]?((\d+\.?\d*)|(\.\d+))"), - (TokenType.Identifier, @"[_a-zA-Z][`'""_a-zA-Z0-9]*"), - (TokenType.Operation, @"[\+\-/\*]"), - (TokenType.OpenParens, @"[([{]"), - (TokenType.CloseParens, @"[)\]}]"), - (TokenType.Equal, @"="), - (TokenType.Comma, @","), - (TokenType.Sum, @"∑") - }; - - static IEnumerable<(TokenType, Regex)> tokenExpressions = - tokenStrings.Select( - t => (t.Item1, new Regex($"^{t.Item2}", RegexOptions.Compiled | RegexOptions.Singleline))); - - // Can be optimized with spans to avoid so many allocations ... - static public Tokens Tokenize(string source) { - var currentLine = 1; - var currentColumn = 1; - - while (source.Length > 0) { - - var matchLength = 0; - var tokenType = TokenType.None; - var value = ""; - - foreach (var (type, rule) in tokenExpressions) { - var match = rule.Match(source); - if(match.Success) { - matchLength = match.Length; - tokenType = type; - value = match.Value; - break; - } + static (TokenType, string)[] tokenStrings = { + (TokenType.EOL, @"(\r\n|\r|\n)"), + (TokenType.Spaces, @"\s+"), + (TokenType.Number, @"[+-]?((\d+\.?\d*)|(\.\d+))"), + (TokenType.Identifier, @"[_a-zA-Z][`'""_a-zA-Z0-9]*"), + (TokenType.Operation, @"[\+\-/\*]"), + (TokenType.OpenParens, @"[([{]"), + (TokenType.CloseParens, @"[)\]}]"), + (TokenType.Equal, @"="), + (TokenType.Comma, @","), + (TokenType.Sum, @"∑") + }; + + static IEnumerable<(TokenType, Regex)> tokenExpressions = + tokenStrings.Select( + t => (t.Item1, new Regex($"^{t.Item2}", RegexOptions.Compiled | RegexOptions.Singleline))); + + // Can be optimized with spans to avoid so many allocations ... + static public Tokens Tokenize(string source) { + var currentLine = 1; + var currentColumn = 1; + + while (source.Length > 0) { + + var matchLength = 0; + var tokenType = TokenType.None; + var value = ""; + + foreach (var (type, rule) in tokenExpressions) { + var match = rule.Match(source); + if(match.Success) { + matchLength = match.Length; + tokenType = type; + value = match.Value; + break; } + } - if (matchLength == 0) { - - throw new Exception($"Unrecognized symbol '{source[currentLine - 1]}' at index {currentLine - 1} (line {currentLine}, column {currentColumn})."); - - } else { + if (matchLength == 0) { - if(tokenType != TokenType.Spaces) - yield return new Token { - Type = tokenType, - Value = value, - Line = currentLine, - Column = currentColumn - }; + throw new Exception($"Unrecognized symbol '{source[currentLine - 1]}' at index {currentLine - 1} (line {currentLine}, column {currentColumn})."); - currentColumn += matchLength; - if(tokenType == TokenType.EOL) { - currentLine += 1; - currentColumn = 0; - } + } else { - source = source.Substring(matchLength); + if(tokenType != TokenType.Spaces) + yield return new Token { + Type = tokenType, + Value = value, + Line = currentLine, + Column = currentColumn + }; + + currentColumn += matchLength; + if(tokenType == TokenType.EOL) { + currentLine += 1; + currentColumn = 0; } - } - yield return new Token { - Type = TokenType.EOF, - Line = currentLine, - Column = currentColumn - }; + source = source.Substring(matchLength); + } } + + yield return new Token { + Type = TokenType.EOF, + Line = currentLine, + Column = currentColumn + }; } +} - /* EBNF for the language - lines = {line} EOF - line = {EOL} identifier [lround args rround] equal expr EOL {EOL} - args = identifier {comma identifier} - expr = [plus|minus] term { (plus|minus) term } - term = factor { (times|divide) factor }; - factor = number | var | func | sum | matrix | lround expr rround; - var = identifier; - func = identifier lround expr {comma expr} rround; - sum = ∑ lround identifier comma expr comma expr comma expr rround; - */ - public static class Parser { - - - public static string Parse(Tokens tokens) { - var globalSymbolTable = new SymTable(); - var symbolTable = new SymTable(); - var buffer = new StringBuilder(); - - var en = tokens.GetEnumerator(); - en.MoveNext(); - - buffer = Lines(new Context { - tokens = en, - globalSymbolTable = globalSymbolTable, - symbolTable = symbolTable, - buffer = buffer - }); - return buffer.ToString(); +/* EBNF for the language + lines = {line} EOF + line = {EOL} identifier [lround args rround] equal expr EOL {EOL} + args = identifier {comma identifier} + expr = [plus|minus] term { (plus|minus) term } + term = factor { (times|divide) factor }; + factor = number | var | func | sum | matrix | lround expr rround; + var = identifier; + func = identifier lround expr {comma expr} rround; + sum = ∑ lround identifier comma expr comma expr comma expr rround; +*/ +public static class Parser { + + + public static string Parse(Tokens tokens) { + var globalSymbolTable = new SymTable(); + var symbolTable = new SymTable(); + var buffer = new StringBuilder(); + + var en = tokens.GetEnumerator(); + en.MoveNext(); + + buffer = Lines(new Context { + tokens = en, + globalSymbolTable = globalSymbolTable, + symbolTable = symbolTable, + buffer = buffer + }); + return buffer.ToString(); - } + } - private readonly static string Preamble = @" + private readonly static string Preamble = @" using static System.Math; using static Maths.FormulaHelpers; @@ -154,261 +155,261 @@ namespace Maths { public static partial class Formulas { "; - private readonly static string Ending = @" + private readonly static string Ending = @" } }"; - private struct Context { - public IEnumerator tokens; - public SymTable globalSymbolTable; - public SymTable symbolTable; - public StringBuilder buffer; - } + private struct Context { + public IEnumerator tokens; + public SymTable globalSymbolTable; + public SymTable symbolTable; + public StringBuilder buffer; + } - private static StringBuilder Error(Token token, TokenType type, string value = "") => - throw new Exception($"Expected {type} {(value == "" ? "" : $" with {token.Value}")} at {token.Line},{token.Column} Instead found {token.Type} with value {token.Value}"); + private static StringBuilder Error(Token token, TokenType type, string value = "") => + throw new Exception($"Expected {type} {(value == "" ? "" : $" with {token.Value}")} at {token.Line},{token.Column} Instead found {token.Type} with value {token.Value}"); - static HashSet validFunctions = - new HashSet(typeof(System.Math).GetMethods().Select(m => m.Name.ToLower())); + static HashSet validFunctions = + new HashSet(typeof(System.Math).GetMethods().Select(m => m.Name.ToLower())); - static Dictionary replacementStrings = new Dictionary { - {"'''", "Third" }, {"''", "Second" }, {"'", "Prime"} - }; + static Dictionary replacementStrings = new Dictionary { + {"'''", "Third" }, {"''", "Second" }, {"'", "Prime"} + }; - private static StringBuilder EmitIdentifier(Context ctx, Token token) { - var val = token.Value; + private static StringBuilder EmitIdentifier(Context ctx, Token token) { + var val = token.Value; - if(val == "pi") { - ctx.buffer.Append("PI"); // Doesn't follow pattern - return ctx.buffer; - } + if(val == "pi") { + ctx.buffer.Append("PI"); // Doesn't follow pattern + return ctx.buffer; + } - if(validFunctions.Contains(val)) { - ctx.buffer.Append(char.ToUpper(val[0]) + val.Substring(1)); - return ctx.buffer; - } + if(validFunctions.Contains(val)) { + ctx.buffer.Append(char.ToUpper(val[0]) + val.Substring(1)); + return ctx.buffer; + } - string id = token.Value; - if(ctx.globalSymbolTable.Contains(token.Value) || - ctx.symbolTable.Contains(token.Value)) { - foreach (var r in replacementStrings) { - id = id.Replace(r.Key, r.Value); - } - return ctx.buffer.Append(id); - } else { - throw new Exception($"{token.Value} not a known identifier or function."); + string id = token.Value; + if(ctx.globalSymbolTable.Contains(token.Value) || + ctx.symbolTable.Contains(token.Value)) { + foreach (var r in replacementStrings) { + id = id.Replace(r.Key, r.Value); } + return ctx.buffer.Append(id); + } else { + throw new Exception($"{token.Value} not a known identifier or function."); } + } - private static StringBuilder Emit(Context ctx, Token token) => token.Type switch - { - TokenType.EOL => ctx.buffer.Append("\n"), - TokenType.CloseParens => ctx.buffer.Append(')'), // All parens become rounded - TokenType.OpenParens => ctx.buffer.Append('('), - TokenType.Equal => ctx.buffer.Append("=>"), - TokenType.Comma => ctx.buffer.Append(token.Value), - - // Identifiers are normalized and checked for injection attacks - TokenType.Identifier => EmitIdentifier(ctx, token), - TokenType.Number => ctx.buffer.Append(token.Value), - TokenType.Operation => ctx.buffer.Append(token.Value), - TokenType.Sum => ctx.buffer.Append("MySum"), - _ => Error(token, TokenType.None) - }; - - private static bool Peek(Context ctx, TokenType type, string value = "") { - var token = ctx.tokens.Current; - - return (token.Type == type && value == "") || - (token.Type == type && value == token.Value); - } - private static Token NextToken(Context ctx) { + private static StringBuilder Emit(Context ctx, Token token) => token.Type switch + { + TokenType.EOL => ctx.buffer.Append("\n"), + TokenType.CloseParens => ctx.buffer.Append(')'), // All parens become rounded + TokenType.OpenParens => ctx.buffer.Append('('), + TokenType.Equal => ctx.buffer.Append("=>"), + TokenType.Comma => ctx.buffer.Append(token.Value), + + // Identifiers are normalized and checked for injection attacks + TokenType.Identifier => EmitIdentifier(ctx, token), + TokenType.Number => ctx.buffer.Append(token.Value), + TokenType.Operation => ctx.buffer.Append(token.Value), + TokenType.Sum => ctx.buffer.Append("MySum"), + _ => Error(token, TokenType.None) + }; + + private static bool Peek(Context ctx, TokenType type, string value = "") { + var token = ctx.tokens.Current; + + return (token.Type == type && value == "") || + (token.Type == type && value == token.Value); + } + private static Token NextToken(Context ctx) { - var token = ctx.tokens.Current; - ctx.tokens.MoveNext(); - return token; - } - private static void Consume(Context ctx, TokenType type, string value = "") { + var token = ctx.tokens.Current; + ctx.tokens.MoveNext(); + return token; + } + private static void Consume(Context ctx, TokenType type, string value = "") { - var token = NextToken(ctx); + var token = NextToken(ctx); - if((token.Type == type && value == "") || - (token.Type == type && value == token.Value)) { + if((token.Type == type && value == "") || + (token.Type == type && value == token.Value)) { - ctx.buffer.Append(" "); - Emit(ctx, token); - } else { - Error(token, type, value); - } + ctx.buffer.Append(" "); + Emit(ctx, token); + } else { + Error(token, type, value); } + } - private static StringBuilder Lines(Context ctx) { - // lines = {line} EOF + private static StringBuilder Lines(Context ctx) { + // lines = {line} EOF - ctx.buffer.Append(Preamble); + ctx.buffer.Append(Preamble); - while(!Peek(ctx, TokenType.EOF)) - Line(ctx); + while(!Peek(ctx, TokenType.EOF)) + Line(ctx); - ctx.buffer.Append(Ending); + ctx.buffer.Append(Ending); - return ctx.buffer; - } + return ctx.buffer; + } - private static void AddGlobalSymbol(Context ctx) { - var token = ctx.tokens.Current; - if(Peek(ctx, TokenType.Identifier)) { - ctx.globalSymbolTable.Add(token.Value); - } else { - Error(token, TokenType.Identifier); - } + private static void AddGlobalSymbol(Context ctx) { + var token = ctx.tokens.Current; + if(Peek(ctx, TokenType.Identifier)) { + ctx.globalSymbolTable.Add(token.Value); + } else { + Error(token, TokenType.Identifier); } - private static void AddSymbol(Context ctx) { - var token = ctx.tokens.Current; - if(Peek(ctx, TokenType.Identifier)) { - ctx.symbolTable.Add(token.Value); - } else { - Error(token, TokenType.Identifier); - } + } + private static void AddSymbol(Context ctx) { + var token = ctx.tokens.Current; + if(Peek(ctx, TokenType.Identifier)) { + ctx.symbolTable.Add(token.Value); + } else { + Error(token, TokenType.Identifier); } - private static void Line(Context ctx) { - // line = {EOL} identifier [lround args rround] equal expr EOL {EOL} + } + private static void Line(Context ctx) { + // line = {EOL} identifier [lround args rround] equal expr EOL {EOL} - ctx.symbolTable.Clear(); + ctx.symbolTable.Clear(); - while(Peek(ctx, TokenType.EOL)) - Consume(ctx, TokenType.EOL); + while(Peek(ctx, TokenType.EOL)) + Consume(ctx, TokenType.EOL); - ctx.buffer.Append("\tpublic static double "); + ctx.buffer.Append("\tpublic static double "); - AddGlobalSymbol(ctx); - Consume(ctx, TokenType.Identifier); + AddGlobalSymbol(ctx); + Consume(ctx, TokenType.Identifier); - if(Peek(ctx, TokenType.OpenParens, "(")) { - Consume(ctx, TokenType.OpenParens, "("); // Just round parens - Args(ctx); - Consume(ctx, TokenType.CloseParens, ")"); - } + if(Peek(ctx, TokenType.OpenParens, "(")) { + Consume(ctx, TokenType.OpenParens, "("); // Just round parens + Args(ctx); + Consume(ctx, TokenType.CloseParens, ")"); + } - Consume(ctx, TokenType.Equal); - Expr(ctx); - ctx.buffer.Append(" ;"); + Consume(ctx, TokenType.Equal); + Expr(ctx); + ctx.buffer.Append(" ;"); + Consume(ctx, TokenType.EOL); + + while(Peek(ctx, TokenType.EOL)) Consume(ctx, TokenType.EOL); + } + private static void Args(Context ctx) { + // args = identifier {comma identifier} + // It doesn't make sense for a math function to have zero args (I think) - while(Peek(ctx, TokenType.EOL)) - Consume(ctx, TokenType.EOL); - } - private static void Args(Context ctx) { - // args = identifier {comma identifier} - // It doesn't make sense for a math function to have zero args (I think) + ctx.buffer.Append("double "); + AddSymbol(ctx); + Consume(ctx, TokenType.Identifier); + while(Peek(ctx, TokenType.Comma)) { + Consume(ctx, TokenType.Comma); ctx.buffer.Append("double "); AddSymbol(ctx); Consume(ctx, TokenType.Identifier); - - while(Peek(ctx, TokenType.Comma)) { - Consume(ctx, TokenType.Comma); - ctx.buffer.Append("double "); - AddSymbol(ctx); - Consume(ctx, TokenType.Identifier); - } } - private static Func IsOp = (ctx, op) - => Peek(ctx, TokenType.Operation, op); - private static Action ConsOp = (ctx, op) - => Consume(ctx, TokenType.Operation, op); + } + private static Func IsOp = (ctx, op) + => Peek(ctx, TokenType.Operation, op); + private static Action ConsOp = (ctx, op) + => Consume(ctx, TokenType.Operation, op); - private static void Expr(Context ctx) { - // expr = [plus|minus] term { (plus|minus) term } + private static void Expr(Context ctx) { + // expr = [plus|minus] term { (plus|minus) term } - if(IsOp(ctx, "+")) ConsOp(ctx, "+"); - if(IsOp(ctx, "-")) ConsOp(ctx, "-"); + if(IsOp(ctx, "+")) ConsOp(ctx, "+"); + if(IsOp(ctx, "-")) ConsOp(ctx, "-"); - Term(ctx); + Term(ctx); - while(IsOp(ctx, "+") || IsOp(ctx, "-")) { + while(IsOp(ctx, "+") || IsOp(ctx, "-")) { - if(IsOp(ctx, "+")) ConsOp(ctx, "+"); - if(IsOp(ctx, "-")) ConsOp(ctx, "-"); + if(IsOp(ctx, "+")) ConsOp(ctx, "+"); + if(IsOp(ctx, "-")) ConsOp(ctx, "-"); - Term(ctx); - } + Term(ctx); } - private static void Term(Context ctx) { - // term = factor { (times|divide) factor }; - Factor(ctx); + } + private static void Term(Context ctx) { + // term = factor { (times|divide) factor }; + Factor(ctx); - while(IsOp(ctx, "*") || IsOp(ctx, "/")) { - if(IsOp(ctx, "*")) ConsOp(ctx, "*"); - if(IsOp(ctx, "/")) ConsOp(ctx, "/"); + while(IsOp(ctx, "*") || IsOp(ctx, "/")) { + if(IsOp(ctx, "*")) ConsOp(ctx, "*"); + if(IsOp(ctx, "/")) ConsOp(ctx, "/"); - Term(ctx); - } + Term(ctx); + } + } + private static void Factor(Context ctx) { + // factor = number | var | func | lround expr rround; + if(Peek(ctx, TokenType.Number)) { + Consume(ctx, TokenType.Number); + return; } - private static void Factor(Context ctx) { - // factor = number | var | func | lround expr rround; - if(Peek(ctx, TokenType.Number)) { - Consume(ctx, TokenType.Number); - return; - } - if(Peek(ctx, TokenType.Identifier)) { - Consume(ctx, TokenType.Identifier); // Is either var or func - if(Peek(ctx, TokenType.OpenParens, "(")) { // Is Func, but we already consumed its name - Funct(ctx); - } - return; - } - if(Peek(ctx, TokenType.Sum)) { - Sum(ctx); - return; + if(Peek(ctx, TokenType.Identifier)) { + Consume(ctx, TokenType.Identifier); // Is either var or func + if(Peek(ctx, TokenType.OpenParens, "(")) { // Is Func, but we already consumed its name + Funct(ctx); } - // Must be a parenthesized expression - Consume(ctx, TokenType.OpenParens); - Expr(ctx); - Consume(ctx, TokenType.CloseParens); + return; } - private static void Sum(Context ctx) { - // sum = ∑ lround identifier comma expr1 comma expr2 comma expr3 rround; - // TODO: differentiate in the language between integer and double, but complicated for a sample. - Consume(ctx, TokenType.Sum); - Consume(ctx, TokenType.OpenParens, "("); + if(Peek(ctx, TokenType.Sum)) { + Sum(ctx); + return; + } + // Must be a parenthesized expression + Consume(ctx, TokenType.OpenParens); + Expr(ctx); + Consume(ctx, TokenType.CloseParens); + } + private static void Sum(Context ctx) { + // sum = ∑ lround identifier comma expr1 comma expr2 comma expr3 rround; + // TODO: differentiate in the language between integer and double, but complicated for a sample. + Consume(ctx, TokenType.Sum); + Consume(ctx, TokenType.OpenParens, "("); - AddSymbol(ctx); - var varName = NextToken(ctx).Value; - NextToken(ctx); // consume the first comma without emitting it + AddSymbol(ctx); + var varName = NextToken(ctx).Value; + NextToken(ctx); // consume the first comma without emitting it - ctx.buffer.Append("(int)"); - Expr(ctx); // Start index - Consume(ctx, TokenType.Comma); + ctx.buffer.Append("(int)"); + Expr(ctx); // Start index + Consume(ctx, TokenType.Comma); - ctx.buffer.Append("(int)"); - Expr(ctx); // End index - Consume(ctx, TokenType.Comma); + ctx.buffer.Append("(int)"); + Expr(ctx); // End index + Consume(ctx, TokenType.Comma); - ctx.buffer.Append($"{varName} => "); // It needs to be a lambda - - Expr(ctx); // expr to evaluate at each iteration + ctx.buffer.Append($"{varName} => "); // It needs to be a lambda + + Expr(ctx); // expr to evaluate at each iteration - Consume(ctx, TokenType.CloseParens, ")"); - } - private static void Funct(Context ctx) { - // func = identifier lround expr {comma expr} rround; - Consume(ctx, TokenType.OpenParens, "("); + Consume(ctx, TokenType.CloseParens, ")"); + } + private static void Funct(Context ctx) { + // func = identifier lround expr {comma expr} rround; + Consume(ctx, TokenType.OpenParens, "("); + Expr(ctx); + while(Peek(ctx, TokenType.Comma)) { + Consume(ctx, TokenType.Comma); Expr(ctx); - while(Peek(ctx, TokenType.Comma)) { - Consume(ctx, TokenType.Comma); - Expr(ctx); - } - Consume(ctx, TokenType.CloseParens, ")"); } + Consume(ctx, TokenType.CloseParens, ")"); } +} - [Generator] - public class MathsGenerator : ISourceGenerator - { - private const string libraryCode = @" +[Generator] +public class MathsGenerator : ISourceGenerator +{ + private const string libraryCode = @" using System.Linq; using System; using System.Collections.Generic; @@ -428,43 +429,42 @@ public static double MySum(int start, int end, Func f) => } "; - public void Execute(GeneratorExecutionContext context) + public void Execute(GeneratorExecutionContext context) + { + foreach (AdditionalText file in context.AdditionalFiles) { - foreach (AdditionalText file in context.AdditionalFiles) + if (Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase)) { - if (Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase)) + // Load formulas from .math files + var mathText = file.GetText(); + var mathString = ""; + + if(mathText != null) { - // Load formulas from .math files - var mathText = file.GetText(); - var mathString = ""; - - if(mathText != null) - { - mathString = mathText.ToString(); - } - else - { - throw new Exception($"Cannot load file {file.Path}"); - } - - // Get name of generated namespace from file name - string fileName = Path.GetFileNameWithoutExtension(file.Path); - - // Parse and gen the formulas functions - var tokens = Lexer.Tokenize(mathString); - var code = Parser.Parse(tokens); - - var codeFileName = $@"{fileName}.cs"; - - context.AddSource(codeFileName, SourceText.From(code, Encoding.UTF8)); + mathString = mathText.ToString(); + } + else + { + throw new Exception($"Cannot load file {file.Path}"); } + + // Get name of generated namespace from file name + string fileName = Path.GetFileNameWithoutExtension(file.Path); + + // Parse and gen the formulas functions + var tokens = Lexer.Tokenize(mathString); + var code = Parser.Parse(tokens); + + var codeFileName = $@"{fileName}.cs"; + + context.AddSource(codeFileName, SourceText.From(code, Encoding.UTF8)); } } + } - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForPostInitialization((pi) => pi.AddSource("__MathLibrary__.cs", libraryCode)); - } + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForPostInitialization((pi) => pi.AddSource("__MathLibrary__.cs", libraryCode)); } } diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs index 51766e7c7..2770f6294 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs @@ -7,12 +7,12 @@ #nullable enable -namespace Mustache +namespace Mustache; + +[Generator] +public class MustacheGenerator : ISourceGenerator { - [Generator] - public class MustacheGenerator : ISourceGenerator - { - private const string attributeSource = @" + private const string attributeSource = @" [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] internal sealed class MustacheAttribute: System.Attribute { @@ -24,24 +24,24 @@ public MustacheAttribute(string name, string template, string hash) } "; - public void Execute(GeneratorExecutionContext context) + public void Execute(GeneratorExecutionContext context) + { + SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!; + foreach ((string name, string template, string hash) in rx.TemplateInfo) { - SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!; - foreach ((string name, string template, string hash) in rx.TemplateInfo) - { - string source = SourceFileFromMustachePath(name, template, hash); - context.AddSource($"Mustache{name}", source); - } + string source = SourceFileFromMustachePath(name, template, hash); + context.AddSource($"Mustache{name}", source); } - - static string SourceFileFromMustachePath(string name, string template, string hash) - { - Func tree = HandlebarsDotNet.Handlebars.Compile(template); - object @object = Newtonsoft.Json.JsonConvert.DeserializeObject(hash); - string mustacheText = tree(@object); + } + + static string SourceFileFromMustachePath(string name, string template, string hash) + { + Func tree = HandlebarsDotNet.Handlebars.Compile(template); + object @object = Newtonsoft.Json.JsonConvert.DeserializeObject(hash); + string mustacheText = tree(@object); - StringBuilder sb = new StringBuilder(); - sb.Append($@" + StringBuilder sb = new StringBuilder(); + sb.Append($@" namespace Mustache {{ public static partial class Constants {{ @@ -50,32 +50,31 @@ public static partial class Constants {{ }} }} "); - return sb.ToString(); - } + return sb.ToString(); + } - public void Initialize(GeneratorInitializationContext context) - { - context.RegisterForPostInitialization((pi) => pi.AddSource("Mustache_MainAttributes__", attributeSource)); - context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); - } + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForPostInitialization((pi) => pi.AddSource("Mustache_MainAttributes__", attributeSource)); + context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + } - class SyntaxReceiver : ISyntaxContextReceiver - { - public List<(string name, string template, string hash)> TemplateInfo = new List<(string name, string template, string hash)>(); + class SyntaxReceiver : ISyntaxContextReceiver + { + public List<(string name, string template, string hash)> TemplateInfo = new List<(string name, string template, string hash)>(); - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + { + // find all valid mustache attributes + if (context.Node is AttributeSyntax attrib + && attrib.ArgumentList?.Arguments.Count == 3 + && context.SemanticModel.GetTypeInfo(attrib).Type?.ToDisplayString() == "MustacheAttribute") { - // find all valid mustache attributes - if (context.Node is AttributeSyntax attrib - && attrib.ArgumentList?.Arguments.Count == 3 - && context.SemanticModel.GetTypeInfo(attrib).Type?.ToDisplayString() == "MustacheAttribute") - { - string name = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[0].Expression).ToString(); - string template = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[1].Expression).ToString(); - string hash = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[2].Expression).ToString(); + string name = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[0].Expression).ToString(); + string template = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[1].Expression).ToString(); + string hash = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[2].Expression).ToString(); - TemplateInfo.Add((name, template, hash)); - } + TemplateInfo.Add((name, template, hash)); } } } diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index 7854de5ef..b67c80f0b 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -7,42 +7,42 @@ using System.Text; using System.Xml; -namespace Analyzer1 +namespace Analyzer1; + +[Generator] +public class SettingsXmlGenerator : ISourceGenerator { - [Generator] - public class SettingsXmlGenerator : ISourceGenerator + public void Execute(GeneratorExecutionContext context) { - public void Execute(GeneratorExecutionContext context) + // Using the context, get any additional files that end in .xmlsettings + IEnumerable settingsFiles = context.AdditionalFiles.Where(at => at.Path.EndsWith(".xmlsettings")); + foreach (AdditionalText settingsFile in settingsFiles) { - // Using the context, get any additional files that end in .xmlsettings - IEnumerable settingsFiles = context.AdditionalFiles.Where(at => at.Path.EndsWith(".xmlsettings")); - foreach (AdditionalText settingsFile in settingsFiles) - { - ProcessSettingsFile(settingsFile, context); - } + ProcessSettingsFile(settingsFile, context); } - - private void ProcessSettingsFile(AdditionalText xmlFile, GeneratorExecutionContext context) + } + + private void ProcessSettingsFile(AdditionalText xmlFile, GeneratorExecutionContext context) + { + // try and load the settings file + XmlDocument xmlDoc = new XmlDocument(); + string text = xmlFile.GetText(context.CancellationToken).ToString(); + try { - // try and load the settings file - XmlDocument xmlDoc = new XmlDocument(); - string text = xmlFile.GetText(context.CancellationToken).ToString(); - try - { - xmlDoc.LoadXml(text); - } - catch - { - //TODO: issue a diagnostic that says we couldn't parse it - return; - } + xmlDoc.LoadXml(text); + } + catch + { + //TODO: issue a diagnostic that says we couldn't parse it + return; + } - - // create a class in the XmlSetting class that represnts this entry, and a static field that contains a singleton instance. - string fileName = Path.GetFileName(xmlFile.Path); - string name = xmlDoc.DocumentElement.GetAttribute("name"); + + // create a class in the XmlSetting class that represnts this entry, and a static field that contains a singleton instance. + string fileName = Path.GetFileName(xmlFile.Path); + string name = xmlDoc.DocumentElement.GetAttribute("name"); - StringBuilder sb = new StringBuilder($@" + StringBuilder sb = new StringBuilder($@" namespace AutoSettings {{ using System; @@ -69,13 +69,13 @@ public class {name}Settings }} "); - for(int i = 0; i < xmlDoc.DocumentElement.ChildNodes.Count; i++) - { - XmlElement setting = (XmlElement)xmlDoc.DocumentElement.ChildNodes[i]; - string settingName = setting.GetAttribute("name"); - string settingType = setting.GetAttribute("type"); + for(int i = 0; i < xmlDoc.DocumentElement.ChildNodes.Count; i++) + { + XmlElement setting = (XmlElement)xmlDoc.DocumentElement.ChildNodes[i]; + string settingName = setting.GetAttribute("name"); + string settingType = setting.GetAttribute("type"); - sb.Append($@" + sb.Append($@" public {settingType} {settingName} {{ @@ -85,15 +85,14 @@ public class {name}Settings }} }} "); - } + } - sb.Append("} } }"); + sb.Append("} } }"); - context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)); - } - - public void Initialize(GeneratorInitializationContext context) - { - } + context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)); + } + + public void Initialize(GeneratorInitializationContext context) + { } } From 113967f125fae8888b8ed5d402898e4ff2a65d14 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 13 Apr 2022 15:35:34 +0200 Subject: [PATCH 02/21] Use new expression. --- .../SourceGeneratorSamples/AutoNotifyGenerator.cs | 2 +- .../SourceGeneratorSamples/CsvGenerator.cs | 4 ++-- .../SourceGeneratorSamples/HelloWorldGenerator.cs | 2 +- .../SourceGeneratorSamples/MathsGenerator.cs | 11 ++++++----- .../SourceGeneratorSamples/MustacheGenerator.cs | 4 ++-- .../SourceGeneratorSamples/SettingsXmlGenerator.cs | 4 ++-- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index 539245420..ac3fe8216 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -66,7 +66,7 @@ private string ProcessClass(INamedTypeSymbol classSymbol, List fie string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); // begin building the generated source - StringBuilder source = new StringBuilder($@" + StringBuilder source = new ($@" namespace {namespaceName} {{ public partial class {classSymbol.Name} : {notifySymbol.ToDisplayString()} diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs index bebfecbaf..aecad2ef8 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs @@ -58,8 +58,8 @@ public static (string[], string[], string[]?) ExtractProperties(CsvTextFieldPars // , loading the collection multiple times instead of once. Measures could be taken to avoid that. public static string GenerateClassFile(string className, string csvText, CsvLoadType loadTime, bool cacheObjects) { - StringBuilder sb = new StringBuilder(); - using CsvTextFieldParser parser = new CsvTextFieldParser(new StringReader(csvText)); + StringBuilder sb = new (); + using CsvTextFieldParser parser = new (new StringReader(csvText)); //// Usings sb.Append(@" diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index cbf8d1a8a..7f6ff8e93 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -12,7 +12,7 @@ public class HelloWorldGenerator : ISourceGenerator public void Execute(GeneratorExecutionContext context) { // begin creating the source we'll inject into the users compilation - StringBuilder sourceBuilder = new StringBuilder(@" + StringBuilder sourceBuilder = new (@" using System; namespace HelloWorldGenerated { diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs index 36603ed3f..81baf5f29 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs @@ -45,7 +45,7 @@ public static void PrintTokens(IEnumerable tokens) { } } - static (TokenType, string)[] tokenStrings = { + static readonly (TokenType, string)[] tokenStrings = { (TokenType.EOL, @"(\r\n|\r|\n)"), (TokenType.Spaces, @"\s+"), (TokenType.Number, @"[+-]?((\d+\.?\d*)|(\.\d+))"), @@ -58,7 +58,7 @@ public static void PrintTokens(IEnumerable tokens) { (TokenType.Sum, @"∑") }; - static IEnumerable<(TokenType, Regex)> tokenExpressions = + static readonly IEnumerable<(TokenType, Regex)> tokenExpressions = tokenStrings.Select( t => (t.Item1, new Regex($"^{t.Item2}", RegexOptions.Compiled | RegexOptions.Singleline))); @@ -170,9 +170,10 @@ private static StringBuilder Error(Token token, TokenType type, string value = " throw new Exception($"Expected {type} {(value == "" ? "" : $" with {token.Value}")} at {token.Line},{token.Column} Instead found {token.Type} with value {token.Value}"); static HashSet validFunctions = - new HashSet(typeof(System.Math).GetMethods().Select(m => m.Name.ToLower())); + new(typeof(System.Math).GetMethods().Select(m => m.Name.ToLower())); - static Dictionary replacementStrings = new Dictionary { + static readonly Dictionary replacementStrings = new() + { {"'''", "Third" }, {"''", "Second" }, {"'", "Prime"} }; @@ -315,7 +316,7 @@ private static void Args(Context ctx) { Consume(ctx, TokenType.Identifier); } } - private static Func IsOp = (ctx, op) + private readonly static Func IsOp = (ctx, op) => Peek(ctx, TokenType.Operation, op); private static Action ConsOp = (ctx, op) => Consume(ctx, TokenType.Operation, op); diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs index 2770f6294..c2ca2efb2 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs @@ -40,7 +40,7 @@ static string SourceFileFromMustachePath(string name, string template, string ha object @object = Newtonsoft.Json.JsonConvert.DeserializeObject(hash); string mustacheText = tree(@object); - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new (); sb.Append($@" namespace Mustache {{ @@ -61,7 +61,7 @@ public void Initialize(GeneratorInitializationContext context) class SyntaxReceiver : ISyntaxContextReceiver { - public List<(string name, string template, string hash)> TemplateInfo = new List<(string name, string template, string hash)>(); + public List<(string name, string template, string hash)> TemplateInfo = new (); public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index b67c80f0b..6c7fcc6c5 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -25,7 +25,7 @@ public void Execute(GeneratorExecutionContext context) private void ProcessSettingsFile(AdditionalText xmlFile, GeneratorExecutionContext context) { // try and load the settings file - XmlDocument xmlDoc = new XmlDocument(); + XmlDocument xmlDoc = new (); string text = xmlFile.GetText(context.CancellationToken).ToString(); try { @@ -42,7 +42,7 @@ private void ProcessSettingsFile(AdditionalText xmlFile, GeneratorExecutionConte string fileName = Path.GetFileName(xmlFile.Path); string name = xmlDoc.DocumentElement.GetAttribute("name"); - StringBuilder sb = new StringBuilder($@" + StringBuilder sb = new($@" namespace AutoSettings {{ using System; From 1c77465e5eb7308701dc3ba46b61285433acb929 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 13 Apr 2022 15:37:00 +0200 Subject: [PATCH 03/21] Local function can be made static. --- .../SourceGeneratorSamples/AutoNotifyGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index ac3fe8216..3536870e4 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -41,7 +41,7 @@ public void Initialize(GeneratorInitializationContext context) public void Execute(GeneratorExecutionContext context) { // retrieve the populated receiver - if (!(context.SyntaxContextReceiver is SyntaxReceiver receiver)) + if (context.SyntaxContextReceiver is not SyntaxReceiver receiver) return; // get the added attribute, and INotifyPropertyChanged @@ -123,7 +123,7 @@ private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbo "); - string chooseName(string fieldName, TypedConstant overridenNameOpt) + static string chooseName(string fieldName, TypedConstant overridenNameOpt) { if (!overridenNameOpt.IsNull) { From 8fa7e69c214fbceb88eef5bdb81544b37af83eb0 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 13 Apr 2022 15:38:11 +0200 Subject: [PATCH 04/21] Make field readonly. --- .../SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs index 81baf5f29..72461427b 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs @@ -169,7 +169,7 @@ private struct Context { private static StringBuilder Error(Token token, TokenType type, string value = "") => throw new Exception($"Expected {type} {(value == "" ? "" : $" with {token.Value}")} at {token.Line},{token.Column} Instead found {token.Type} with value {token.Value}"); - static HashSet validFunctions = + static readonly SymTable validFunctions = new(typeof(System.Math).GetMethods().Select(m => m.Name.ToLower())); static readonly Dictionary replacementStrings = new() @@ -318,7 +318,7 @@ private static void Args(Context ctx) { } private readonly static Func IsOp = (ctx, op) => Peek(ctx, TokenType.Operation, op); - private static Action ConsOp = (ctx, op) + private static readonly Action ConsOp = (ctx, op) => Consume(ctx, TokenType.Operation, op); private static void Expr(Context ctx) { From 12cd9d488c941d5b24bb319bf79e1f82329806dd Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 13 Apr 2022 15:38:50 +0200 Subject: [PATCH 05/21] Remove redundant assignment. --- .../SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs index 72461427b..4a5863c48 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs @@ -438,9 +438,9 @@ public void Execute(GeneratorExecutionContext context) { // Load formulas from .math files var mathText = file.GetText(); - var mathString = ""; + string mathString; - if(mathText != null) + if (mathText != null) { mathString = mathText.ToString(); } From 4a66428916ce3156def21b35bfc829dea4829624 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 13 Apr 2022 15:44:05 +0200 Subject: [PATCH 06/21] Namespace simplification. --- .../GeneratedDemo/CSharpGeneratedDemo.csproj | 1 + .../SourceGenerators/GeneratedDemo/Program.cs | 35 ++++++----- .../GeneratedDemo/UseAutoNotifyGenerator.cs | 59 +++++++++---------- .../GeneratedDemo/UseCsvGenerator.cs | 17 +++--- .../GeneratedDemo/UseHelloWorldGenerator.cs | 13 ++-- .../GeneratedDemo/UseMathsGenerator.cs | 15 +++-- .../GeneratedDemo/UseMustacheGenerator.cs | 43 +++++++------- .../GeneratedDemo/UseXmlSettingsGenerator.cs | 31 +++++----- .../CSharpSourceGeneratorSamples.csproj | 2 +- 9 files changed, 105 insertions(+), 111 deletions(-) diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj index 4b3054e9f..650c9bad6 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/CSharpGeneratedDemo.csproj @@ -3,6 +3,7 @@ Exe netcoreapp3.1 + 10.0 diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs index 297582c51..49f29005e 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/Program.cs @@ -1,29 +1,28 @@ using System; -namespace GeneratedDemo +namespace GeneratedDemo; + +class Program { - class Program + static void Main() { - static void Main(string[] args) - { - // Run the various scenarios - Console.WriteLine("Running HelloWorld:\n"); - UseHelloWorldGenerator.Run(); + // Run the various scenarios + Console.WriteLine("Running HelloWorld:\n"); + UseHelloWorldGenerator.Run(); - Console.WriteLine("\n\nRunning AutoNotify:\n"); - UseAutoNotifyGenerator.Run(); + Console.WriteLine("\n\nRunning AutoNotify:\n"); + UseAutoNotifyGenerator.Run(); - Console.WriteLine("\n\nRunning XmlSettings:\n"); - UseXmlSettingsGenerator.Run(); + Console.WriteLine("\n\nRunning XmlSettings:\n"); + UseXmlSettingsGenerator.Run(); - Console.WriteLine("\n\nRunning CsvGenerator:\n"); - UseCsvGenerator.Run(); + Console.WriteLine("\n\nRunning CsvGenerator:\n"); + UseCsvGenerator.Run(); - Console.WriteLine("\n\nRunning MustacheGenerator:\n"); - UseMustacheGenerator.Run(); + Console.WriteLine("\n\nRunning MustacheGenerator:\n"); + UseMustacheGenerator.Run(); - Console.WriteLine("\n\nRunning MathsGenerator:\n"); - UseMathsGenerator.Run(); - } + Console.WriteLine("\n\nRunning MathsGenerator:\n"); + UseMathsGenerator.Run(); } } diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs index 77e346c9a..0325b80b8 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseAutoNotifyGenerator.cs @@ -1,39 +1,38 @@ using System; using AutoNotify; -namespace GeneratedDemo +namespace GeneratedDemo; + +// The view model we'd like to augment +public partial class ExampleViewModel { - // The view model we'd like to augment - public partial class ExampleViewModel - { - [AutoNotify] - private string _text = "private field text"; + [AutoNotify] + private string _text = "private field text"; - [AutoNotify(PropertyName = "Count")] - private int _amount = 5; - } + [AutoNotify(PropertyName = "Count")] + private int _amount = 5; +} - public static class UseAutoNotifyGenerator +public static class UseAutoNotifyGenerator +{ + public static void Run() { - public static void Run() - { - ExampleViewModel vm = new ExampleViewModel(); - - // we didn't explicitly create the 'Text' property, it was generated for us - string text = vm.Text; - Console.WriteLine($"Text = {text}"); - - // Properties can have differnt names generated based on the PropertyName argument of the attribute - int count = vm.Count; - Console.WriteLine($"Count = {count}"); - - // the viewmodel will automatically implement INotifyPropertyChanged - vm.PropertyChanged += (o, e) => Console.WriteLine($"Property {e.PropertyName} was changed"); - vm.Text = "abc"; - vm.Count = 123; - - // Try adding fields to the ExampleViewModel class above and tagging them with the [AutoNotify] attribute - // You'll see the matching generated properties visibile in IntelliSense in realtime - } + ExampleViewModel vm = new ExampleViewModel(); + + // we didn't explicitly create the 'Text' property, it was generated for us + string text = vm.Text; + Console.WriteLine($"Text = {text}"); + + // Properties can have differnt names generated based on the PropertyName argument of the attribute + int count = vm.Count; + Console.WriteLine($"Count = {count}"); + + // the viewmodel will automatically implement INotifyPropertyChanged + vm.PropertyChanged += (o, e) => Console.WriteLine($"Property {e.PropertyName} was changed"); + vm.Text = "abc"; + vm.Count = 123; + + // Try adding fields to the ExampleViewModel class above and tagging them with the [AutoNotify] attribute + // You'll see the matching generated properties visibile in IntelliSense in realtime } } diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs index 29be8efaf..b619a90c7 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseCsvGenerator.cs @@ -5,16 +5,15 @@ using static System.Console; using CSV; -namespace GeneratedDemo +namespace GeneratedDemo; + +class UseCsvGenerator { - class UseCsvGenerator + public static void Run() { - public static void Run() - { - WriteLine("## CARS"); - Cars.All.ToList().ForEach(c => WriteLine($"{c.Brand}\t{c.Model}\t{c.Year}\t{c.Cc}")); - WriteLine("\n## PEOPLE"); - People.All.ToList().ForEach(p => WriteLine($"{p.Name}\t{p.Address}\t{p._11Age}")); - } + WriteLine("## CARS"); + Cars.All.ToList().ForEach(c => WriteLine($"{c.Brand}\t{c.Model}\t{c.Year}\t{c.Cc}")); + WriteLine("\n## PEOPLE"); + People.All.ToList().ForEach(p => WriteLine($"{p.Name}\t{p.Address}\t{p._11Age}")); } } diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs index 000328660..f40871fab 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseHelloWorldGenerator.cs @@ -1,11 +1,10 @@ -namespace GeneratedDemo +namespace GeneratedDemo; + +public static class UseHelloWorldGenerator { - public static class UseHelloWorldGenerator + public static void Run() { - public static void Run() - { - // The static call below is generated at build time, and will list the syntax trees used in the compilation - HelloWorldGenerated.HelloWorld.SayHello(); - } + // The static call below is generated at build time, and will list the syntax trees used in the compilation + HelloWorldGenerated.HelloWorld.SayHello(); } } diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs index 2c8b7ecf0..7523e3d1e 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMathsGenerator.cs @@ -1,15 +1,14 @@ using static System.Console; using Maths; -namespace GeneratedDemo +namespace GeneratedDemo; + +public static class UseMathsGenerator { - public static class UseMathsGenerator + public static void Run() { - public static void Run() - { - WriteLine($"The area of a (10, 5) rectangle is: {Formulas.AreaRectangle(10, 5)}"); - WriteLine($"The area of a (10) square is: {Formulas.AreaSquare(10)}"); - WriteLine($"The GoldHarmon of 3 is: {Formulas.GoldHarm(3)}"); - } + WriteLine($"The area of a (10, 5) rectangle is: {Formulas.AreaRectangle(10, 5)}"); + WriteLine($"The area of a (10) square is: {Formulas.AreaSquare(10)}"); + WriteLine($"The GoldHarmon of 3 is: {Formulas.GoldHarm(3)}"); } } diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs index 992d56800..e749b1270 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs @@ -12,28 +12,28 @@ [assembly: Mustache("Section", t4, h4)] [assembly: Mustache("NestedSection", t5, h5)] -namespace GeneratedDemo +namespace GeneratedDemo; + +class UseMustacheGenerator { - class UseMustacheGenerator + public static void Run() { - public static void Run() - { - WriteLine(Mustache.Constants.Lottery); - WriteLine(Mustache.Constants.HR); - WriteLine(Mustache.Constants.HTML); - WriteLine(Mustache.Constants.Section); - WriteLine(Mustache.Constants.NestedSection); - } + WriteLine(Mustache.Constants.Lottery); + WriteLine(Mustache.Constants.HR); + WriteLine(Mustache.Constants.HTML); + WriteLine(Mustache.Constants.Section); + WriteLine(Mustache.Constants.NestedSection); + } - // Mustache templates and hashes from the manual at https://mustache.github.io/mustache.1.html... - public const string t1 = @" + // Mustache templates and hashes from the manual at https://mustache.github.io/mustache.1.html... + public const string t1 = @" Hello {{name}} You have just won {{value}} dollars! {{#in_ca}} Well, {{taxed_value}} dollars, after taxes. {{/in_ca}} "; - public const string h1 = @" + public const string h1 = @" { ""name"": ""Chris"", ""value"": 10000, @@ -41,35 +41,35 @@ You have just won {{value}} dollars! ""in_ca"": true } "; - public const string t2 = @" + public const string t2 = @" * {{name}} * {{age}} * {{company}} * {{{company}}} "; - public const string h2 = @" + public const string h2 = @" { ""name"": ""Chris"", ""company"": ""GitHub"" } "; - public const string t3 = @" + public const string t3 = @" Shown {{#person}} Never shown! {{/person}} "; - public const string h3 = @" + public const string h3 = @" { ""person"": false } "; - public const string t4 = @" + public const string t4 = @" {{#repo}} {{name}} {{/repo}} "; - public const string h4 = @" + public const string h4 = @" { ""repo"": [ { ""name"": ""resque"" }, @@ -78,7 +78,7 @@ Never shown! ] } "; - public const string t5 = @" + public const string t5 = @" {{#repo}} {{name}} {{#nested}} @@ -86,7 +86,7 @@ Never shown! {{/nested}} {{/repo}} "; - public const string h5 = @" + public const string h5 = @" { ""repo"": [ { ""name"": ""resque"", ""nested"":[{""name"":""nestedResque""}] }, @@ -96,5 +96,4 @@ Never shown! } "; - } } diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs index 213181e51..d5631238e 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseXmlSettingsGenerator.cs @@ -1,27 +1,26 @@ using System; using AutoSettings; -namespace GeneratedDemo +namespace GeneratedDemo; + +public static class UseXmlSettingsGenerator { - public static class UseXmlSettingsGenerator + public static void Run() { - public static void Run() - { - // This XmlSettings generator makes a static property in the XmlSettings class for each .xmlsettings file + // This XmlSettings generator makes a static property in the XmlSettings class for each .xmlsettings file - // here we have the 'Main' settings file from MainSettings.xmlsettings - // the name is determined by the 'name' attribute of the root settings element - XmlSettings.MainSettings main = XmlSettings.Main; - Console.WriteLine($"Reading settings from {main.GetLocation()}"); + // here we have the 'Main' settings file from MainSettings.xmlsettings + // the name is determined by the 'name' attribute of the root settings element + XmlSettings.MainSettings main = XmlSettings.Main; + Console.WriteLine($"Reading settings from {main.GetLocation()}"); - // settings are strongly typed and can be read directly from the static instance - bool firstRun = XmlSettings.Main.FirstRun; - Console.WriteLine($"Setting firstRun = {firstRun}"); + // settings are strongly typed and can be read directly from the static instance + bool firstRun = XmlSettings.Main.FirstRun; + Console.WriteLine($"Setting firstRun = {firstRun}"); - int cacheSize = XmlSettings.Main.CacheSize; - Console.WriteLine($"Setting cacheSize = {cacheSize}"); + int cacheSize = XmlSettings.Main.CacheSize; + Console.WriteLine($"Setting cacheSize = {cacheSize}"); - // Try adding some keys to the settings file and see the settings become available to read from - } + // Try adding some keys to the settings file and see the settings become available to read from } } diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj index b10063ca7..d09d9f354 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj @@ -7,7 +7,7 @@ - + From b6d9cc9023cfd70768da76332c12e19c72e426a7 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 13 Apr 2022 17:16:15 +0200 Subject: [PATCH 07/21] HelloWorldGenerator moved to IncrementalGenerator and changed to display all classes to showcase the predicate and transform functions. --- .../SourceGeneratorSamples/.editorconfig | 31 ++++++++++ .../HelloWorldGenerator.cs | 61 +++++++++++++------ 2 files changed, 73 insertions(+), 19 deletions(-) create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig new file mode 100644 index 000000000..8d31544d4 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig @@ -0,0 +1,31 @@ + +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = true:error +dotnet_style_qualification_for_property = true:error +dotnet_style_qualification_for_method = true:error +dotnet_style_qualification_for_event = true:error + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:error +dotnet_style_predefined_type_for_member_access = true:error + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:error +dotnet_style_collection_initializer = true:error +dotnet_style_coalesce_expression = true:error +dotnet_style_null_propagation = true:error +dotnet_style_explicit_tuple_names = true:error + +# CSharp code style settings: +[*.cs] +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = true:error +csharp_style_var_when_type_is_apparent = true:error +csharp_style_var_elsewhere = true:error + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:error +csharp_style_pattern_matching_over_as_with_null_check = true:error +csharp_style_inlined_variable_declaration = true:error +csharp_style_throw_expression = true:error +csharp_style_conditional_delegate_call = true:error diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index 7f6ff8e93..cd7b442ec 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -1,18 +1,46 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Text; +using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace SourceGeneratorSamples; [Generator] -public class HelloWorldGenerator : ISourceGenerator +public class HelloWorldGenerator : IIncrementalGenerator { - public void Execute(GeneratorExecutionContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - // begin creating the source we'll inject into the users compilation - StringBuilder sourceBuilder = new (@" + // Select class syntax nodes and collect their type symbols. + var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( + predicate: IsClassDeclaration, + transform: GetTypeSymbols + ).Collect(); + + // Register a function to generate the code using the collected type symbols. + context.RegisterSourceOutput(classDeclarations, GenerateSource); + } + + // Predicate function: just pick up the class syntax nodes. + private bool IsClassDeclaration(SyntaxNode s, CancellationToken t) => s is ClassDeclarationSyntax; + + // Transform function goes from SyntaxNode to ITypeSymbol. + private ITypeSymbol GetTypeSymbols(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + if (context.SemanticModel.GetDeclaredSymbol(context.Node, cancellationToken) is ITypeSymbol typeSymbol) + return typeSymbol; + + return null; + } + + // Main function to generate the source code. + private void GenerateSource(SourceProductionContext context, ImmutableArray typeSymbols) + { + // Begin creating the source we'll inject into the users compilation. + StringBuilder sourceBuilder = new(@" using System; namespace HelloWorldGenerated { @@ -21,30 +49,25 @@ public static class HelloWorld public static void SayHello() { Console.WriteLine(""Hello from generated code!""); - Console.WriteLine(""The following syntax trees existed in the compilation that created this program:""); + Console.WriteLine(""The following classes existed in the compilation that created this program:""); "); - // using the context, get a list of syntax trees in the users compilation - IEnumerable syntaxTrees = context.Compilation.SyntaxTrees; - - // add the filepath of each tree to the class we're building - foreach (SyntaxTree tree in syntaxTrees) + // Print out each symbol we find. + foreach (var symbol in typeSymbols) { - sourceBuilder.AppendLine($@"Console.WriteLine(@"" - {tree.FilePath}"");"); + // TODO: Why can the symbol be null? + if (symbol is null) + continue; + + sourceBuilder.AppendLine($"Console.WriteLine(\"{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}\");"); } - // finish creating the source to inject + // Finish creating the source to inject. sourceBuilder.Append(@" } } }"); - - // inject the created source into the users compilation - context.AddSource("helloWorldGenerated", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + context.AddSource($"hello_world.g.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); } - public void Initialize(GeneratorInitializationContext context) - { - // No initialization required - } } From f16a78124bcd603861bf44bce437e9ec2fdb7afd Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Thu, 14 Apr 2022 09:19:16 +0200 Subject: [PATCH 08/21] Adding select to the pipeline. --- .../SourceGeneratorSamples/HelloWorldGenerator.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index cd7b442ec..b46541d15 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -14,11 +14,12 @@ public class HelloWorldGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - // Select class syntax nodes and collect their type symbols. + // Select class syntax nodes, transform to type symbols and collect their names. var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( predicate: IsClassDeclaration, transform: GetTypeSymbols - ).Collect(); + ).Select((t, c) => t.Name) + .Collect(); // Register a function to generate the code using the collected type symbols. context.RegisterSourceOutput(classDeclarations, GenerateSource); @@ -37,7 +38,7 @@ private ITypeSymbol GetTypeSymbols(GeneratorSyntaxContext context, CancellationT } // Main function to generate the source code. - private void GenerateSource(SourceProductionContext context, ImmutableArray typeSymbols) + private void GenerateSource(SourceProductionContext context, ImmutableArray typeNames) { // Begin creating the source we'll inject into the users compilation. StringBuilder sourceBuilder = new(@" @@ -52,14 +53,14 @@ public static void SayHello() Console.WriteLine(""The following classes existed in the compilation that created this program:""); "); - // Print out each symbol we find. - foreach (var symbol in typeSymbols) + // Print out each symbol name we find. + foreach (var name in typeNames) { // TODO: Why can the symbol be null? - if (symbol is null) + if (name is null) continue; - sourceBuilder.AppendLine($"Console.WriteLine(\"{symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}\");"); + sourceBuilder.AppendLine($"Console.WriteLine(\"{name}\");"); } // Finish creating the source to inject. From 612cda6b2fb2208227cf6bee7c996863c285aaa7 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Thu, 14 Apr 2022 09:29:56 +0200 Subject: [PATCH 09/21] Introducing global usings. --- .../SourceGeneratorSamples/.editorconfig | 78 ++++++++++++++++++- .../AutoNotifyGenerator.cs | 15 +--- .../SourceGeneratorSamples/CsvGenerator.cs | 14 +--- .../SourceGeneratorSamples/Globals.cs | 21 +++++ .../HelloWorldGenerator.cs | 11 +-- .../SourceGeneratorSamples/MathsGenerator.cs | 17 +--- .../MustacheGenerator.cs | 9 +-- .../SettingsXmlGenerator.cs | 11 +-- 8 files changed, 104 insertions(+), 72 deletions(-) create mode 100644 samples/CSharp/SourceGenerators/SourceGeneratorSamples/Globals.cs diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig index 8d31544d4..bb06d5e60 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/.editorconfig @@ -19,9 +19,9 @@ dotnet_style_explicit_tuple_names = true:error # CSharp code style settings: [*.cs] # Prefer "var" everywhere -csharp_style_var_for_built_in_types = true:error -csharp_style_var_when_type_is_apparent = true:error -csharp_style_var_elsewhere = true:error +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent # Suggest more modern language features when available csharp_style_pattern_matching_over_is_with_cast_check = true:error @@ -29,3 +29,75 @@ csharp_style_pattern_matching_over_as_with_null_check = true:error csharp_style_inlined_variable_declaration = true:error csharp_style_throw_expression = true:error csharp_style_conditional_delegate_call = true:error +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_indent_labels = one_less_than_current +csharp_style_deconstructed_variable_declaration = true:suggestion + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_collection_initializer = true:suggestion diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index 3536870e4..abdc5bb50 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; - -namespace SourceGeneratorSamples; +namespace SourceGeneratorSamples; [Generator] public class AutoNotifyGenerator : ISourceGenerator @@ -51,12 +42,12 @@ public void Execute(GeneratorExecutionContext context) // group the fields by class, and generate the source foreach (IGrouping group in receiver.Fields.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) { - string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context); + string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol); context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); } } - private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol, GeneratorExecutionContext context) + private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol) { if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) { diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs index aecad2ef8..548f9ed8d 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs @@ -1,15 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using NotVisualBasic.FileIO; - -#nullable enable - -// CsvTextFileParser from https://github.com/22222/CsvTextFieldParser adding suppression rules for default VS config +#nullable enable namespace CsvGenerator; @@ -181,4 +170,3 @@ public void Initialize(GeneratorInitializationContext context) { } } -#pragma warning restore IDE0008 // Use explicit type diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Globals.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Globals.cs new file mode 100644 index 000000000..61591c2c3 --- /dev/null +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/Globals.cs @@ -0,0 +1,21 @@ +global using System; +global using System.Collections.Generic; +global using System.Collections.Immutable; +global using System.Text; +global using System.Threading; +global using Microsoft.CodeAnalysis; +global using Microsoft.CodeAnalysis.CSharp.Syntax; +global using Microsoft.CodeAnalysis.Text; +global using System.Linq; +global using Microsoft.CodeAnalysis.CSharp; +global using System.IO; +global using NotVisualBasic.FileIO; +global using System.Text.RegularExpressions; +global using Tokens = System.Collections.Generic.IEnumerable; +global using SymTable = System.Collections.Generic.HashSet; +global using System.Xml; + +global using static System.Console; + + + diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index b46541d15..83f356afe 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -1,13 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Text; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; - -namespace SourceGeneratorSamples; +namespace SourceGeneratorSamples; [Generator] public class HelloWorldGenerator : IIncrementalGenerator diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs index 4a5863c48..538b1bb07 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs @@ -1,19 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; -using static System.Console; -using Tokens = System.Collections.Generic.IEnumerable; -using SymTable = System.Collections.Generic.HashSet; -using System.Linq; -using Microsoft.CodeAnalysis; -using System.IO; -using Microsoft.CodeAnalysis.Text; -using System.Diagnostics; - -#pragma warning disable IDE0008 // Use explicit type - -namespace MathsGenerator; +namespace MathsGenerator; public enum TokenType { Number, diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs index c2ca2efb2..98500a4fa 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs @@ -1,11 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -#nullable enable +#nullable enable namespace Mustache; diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index 6c7fcc6c5..ed723fe3b 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -1,13 +1,4 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml; - -namespace Analyzer1; +namespace Analyzer1; [Generator] public class SettingsXmlGenerator : ISourceGenerator From 84c5c01f50ebdfc84e64b744980a224ac48778c9 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Thu, 14 Apr 2022 09:37:42 +0200 Subject: [PATCH 10/21] Make code nullable safe (apart from AutoNotifyGenerator) --- .../SourceGeneratorSamples/AutoNotifyGenerator.cs | 2 ++ .../CSharpSourceGeneratorSamples.csproj | 1 + .../SourceGeneratorSamples/CsvGenerator.cs | 4 +--- .../SourceGeneratorSamples/HelloWorldGenerator.cs | 7 +++---- .../SourceGeneratorSamples/MustacheGenerator.cs | 4 +--- .../SourceGeneratorSamples/SettingsXmlGenerator.cs | 4 +++- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index abdc5bb50..83c512742 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -1,5 +1,7 @@ namespace SourceGeneratorSamples; +#nullable disable // TODO: make this generator nullable safe. + [Generator] public class AutoNotifyGenerator : ISourceGenerator { diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj index d09d9f354..3c1472887 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CSharpSourceGeneratorSamples.csproj @@ -3,6 +3,7 @@ netstandard2.0 10.0 + enable diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs index 548f9ed8d..31ffe94ea 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs @@ -1,6 +1,4 @@ -#nullable enable - -namespace CsvGenerator; +namespace CsvGenerator; [Generator] public class CSVGenerator : ISourceGenerator diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index 83f356afe..143e2b938 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -9,7 +9,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( predicate: IsClassDeclaration, transform: GetTypeSymbols - ).Select((t, c) => t.Name) + ).Select((t, c) => t?.Name) .Collect(); // Register a function to generate the code using the collected type symbols. @@ -20,7 +20,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private bool IsClassDeclaration(SyntaxNode s, CancellationToken t) => s is ClassDeclarationSyntax; // Transform function goes from SyntaxNode to ITypeSymbol. - private ITypeSymbol GetTypeSymbols(GeneratorSyntaxContext context, CancellationToken cancellationToken) + private ITypeSymbol? GetTypeSymbols(GeneratorSyntaxContext context, CancellationToken cancellationToken) { if (context.SemanticModel.GetDeclaredSymbol(context.Node, cancellationToken) is ITypeSymbol typeSymbol) return typeSymbol; @@ -29,7 +29,7 @@ private ITypeSymbol GetTypeSymbols(GeneratorSyntaxContext context, CancellationT } // Main function to generate the source code. - private void GenerateSource(SourceProductionContext context, ImmutableArray typeNames) + private void GenerateSource(SourceProductionContext context, ImmutableArray typeNames) { // Begin creating the source we'll inject into the users compilation. StringBuilder sourceBuilder = new(@" @@ -47,7 +47,6 @@ public static void SayHello() // Print out each symbol name we find. foreach (var name in typeNames) { - // TODO: Why can the symbol be null? if (name is null) continue; diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs index 98500a4fa..7a0c7ec15 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs @@ -1,6 +1,4 @@ -#nullable enable - -namespace Mustache; +namespace Mustache; [Generator] public class MustacheGenerator : ISourceGenerator diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index ed723fe3b..45da3fe62 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -17,7 +17,9 @@ private void ProcessSettingsFile(AdditionalText xmlFile, GeneratorExecutionConte { // try and load the settings file XmlDocument xmlDoc = new (); - string text = xmlFile.GetText(context.CancellationToken).ToString(); + var text = xmlFile.GetText(context.CancellationToken)?.ToString(); + if(text is null) throw new Exception("Error reading the settings file"); + try { xmlDoc.LoadXml(text); From 02440a09989c1a331f9c8ecff047d428f1f444a0 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Thu, 14 Apr 2022 11:14:51 +0200 Subject: [PATCH 11/21] Add where clause to filter out nulls. --- .../SourceGeneratorSamples/HelloWorldGenerator.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index 143e2b938..30e0e1db5 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -8,8 +8,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Select class syntax nodes, transform to type symbols and collect their names. var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( predicate: IsClassDeclaration, - transform: GetTypeSymbols - ).Select((t, c) => t?.Name) + transform: GetTypeSymbol + ).Where(t => t is not null) + .Select((t, c) => t!.Name) .Collect(); // Register a function to generate the code using the collected type symbols. @@ -20,7 +21,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private bool IsClassDeclaration(SyntaxNode s, CancellationToken t) => s is ClassDeclarationSyntax; // Transform function goes from SyntaxNode to ITypeSymbol. - private ITypeSymbol? GetTypeSymbols(GeneratorSyntaxContext context, CancellationToken cancellationToken) + private ITypeSymbol? GetTypeSymbol(GeneratorSyntaxContext context, CancellationToken cancellationToken) { if (context.SemanticModel.GetDeclaredSymbol(context.Node, cancellationToken) is ITypeSymbol typeSymbol) return typeSymbol; @@ -29,7 +30,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } // Main function to generate the source code. - private void GenerateSource(SourceProductionContext context, ImmutableArray typeNames) + private void GenerateSource(SourceProductionContext context, ImmutableArray typeNames) { // Begin creating the source we'll inject into the users compilation. StringBuilder sourceBuilder = new(@" @@ -46,12 +47,7 @@ public static void SayHello() // Print out each symbol name we find. foreach (var name in typeNames) - { - if (name is null) - continue; - sourceBuilder.AppendLine($"Console.WriteLine(\"{name}\");"); - } // Finish creating the source to inject. sourceBuilder.Append(@" From 215fdd3599382b9d5ca57511958b2d5f1f715b42 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Thu, 14 Apr 2022 11:37:29 +0200 Subject: [PATCH 12/21] Add static code generration. --- .../HelloWorldGenerator.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index 30e0e1db5..d90407e90 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -5,8 +5,17 @@ public class HelloWorldGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - // Select class syntax nodes, transform to type symbols and collect their names. - var classDeclarations = context.SyntaxProvider.CreateSyntaxProvider( + // Use this method to generate constant code (aka that doesn't depend on the syntax tree). + context.RegisterPostInitializationOutput(context => + context.AddSource("AStaticFunc.g.cs", @" + namespace HelloWorldGenerated; + static class Printer { + internal static void PrintUpper(string s) => System.Console.WriteLine(s.ToUpper()); + }") + ); + + // Dependency pipeline. Select class syntax nodes, transform to type symbols and collect their names. + var classNames = context.SyntaxProvider.CreateSyntaxProvider( predicate: IsClassDeclaration, transform: GetTypeSymbol ).Where(t => t is not null) @@ -14,7 +23,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Collect(); // Register a function to generate the code using the collected type symbols. - context.RegisterSourceOutput(classDeclarations, GenerateSource); + context.RegisterSourceOutput(classNames, GenerateSource); } // Predicate function: just pick up the class syntax nodes. @@ -35,13 +44,12 @@ private void GenerateSource(SourceProductionContext context, ImmutableArray Date: Thu, 14 Apr 2022 13:59:11 +0200 Subject: [PATCH 13/21] Add static to lambdas and make everything lambda to make it fit on one page. --- .../HelloWorldGenerator.cs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index d90407e90..b31e18d94 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -16,28 +16,16 @@ static class Printer { // Dependency pipeline. Select class syntax nodes, transform to type symbols and collect their names. var classNames = context.SyntaxProvider.CreateSyntaxProvider( - predicate: IsClassDeclaration, - transform: GetTypeSymbol - ).Where(t => t is not null) - .Select((t, c) => t!.Name) + predicate: static (sn, c) => sn is ClassDeclarationSyntax, + transform: static (ct, c) => ct.SemanticModel.GetDeclaredSymbol(ct.Node, c)) + .Where (static t => t is not null) + .Select (static (t, c) => t!.Name) .Collect(); // Register a function to generate the code using the collected type symbols. context.RegisterSourceOutput(classNames, GenerateSource); } - // Predicate function: just pick up the class syntax nodes. - private bool IsClassDeclaration(SyntaxNode s, CancellationToken t) => s is ClassDeclarationSyntax; - - // Transform function goes from SyntaxNode to ITypeSymbol. - private ITypeSymbol? GetTypeSymbol(GeneratorSyntaxContext context, CancellationToken cancellationToken) - { - if (context.SemanticModel.GetDeclaredSymbol(context.Node, cancellationToken) is ITypeSymbol typeSymbol) - return typeSymbol; - - return null; - } - // Main function to generate the source code. private void GenerateSource(SourceProductionContext context, ImmutableArray typeNames) { From a38010b5952d6ce042791656d2f5f24237b17da1 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Thu, 14 Apr 2022 14:00:01 +0200 Subject: [PATCH 14/21] Align --- .../SourceGeneratorSamples/HelloWorldGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs index b31e18d94..35a5c7476 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/HelloWorldGenerator.cs @@ -18,8 +18,8 @@ static class Printer { var classNames = context.SyntaxProvider.CreateSyntaxProvider( predicate: static (sn, c) => sn is ClassDeclarationSyntax, transform: static (ct, c) => ct.SemanticModel.GetDeclaredSymbol(ct.Node, c)) - .Where (static t => t is not null) - .Select (static (t, c) => t!.Name) + .Where (static t => t is not null) + .Select (static (t, c) => t!.Name) .Collect(); // Register a function to generate the code using the collected type symbols. From bb8864d695fde961a1cc3db714a8103d7cf3e516 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Tue, 26 Apr 2022 09:51:11 +0200 Subject: [PATCH 15/21] Conver the CsvGenerator --- .../SourceGeneratorSamples/CsvGenerator.cs | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs index 31ffe94ea..ab1c834b8 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/CsvGenerator.cs @@ -1,7 +1,10 @@ -namespace CsvGenerator; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CsvGenerator; [Generator] -public class CSVGenerator : ISourceGenerator +public class CSVGenerator : IIncrementalGenerator { public enum CsvLoadType { @@ -135,36 +138,37 @@ static string StringToValidPropertyName(string s) return new (string, string)[] { (className, GenerateClassFile(className, csvText, loadTime, cacheObjects)) }; } - static IEnumerable<(string, string)> SourceFilesFromAdditionalFiles(IEnumerable<(CsvLoadType loadTime, bool cacheObjects, AdditionalText file)> pathsData) - => pathsData.SelectMany(d => SourceFilesFromAdditionalFile(d.loadTime, d.cacheObjects, d.file)); - - static IEnumerable<(CsvLoadType, bool, AdditionalText)> GetLoadOptions(GeneratorExecutionContext context) + public (CsvLoadType, bool, AdditionalText) GetLoadOptionProvider( + (AdditionalText file, AnalyzerConfigOptionsProvider config) source, + CancellationToken ct) { - foreach (AdditionalText file in context.AdditionalFiles) - { - if (Path.GetExtension(file.Path).Equals(".csv", StringComparison.OrdinalIgnoreCase)) - { - // are there any options for it? - context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CsvLoadType", out string? loadTimeString); - Enum.TryParse(loadTimeString, ignoreCase: true, out CsvLoadType loadType); + var options = source.config.GetOptions(source.file); + + options.TryGetValue("build_metadata.additionalfiles.CsvLoadType", out string? loadTimeString); + Enum.TryParse(loadTimeString, ignoreCase: true, out CsvLoadType loadType); - context.AnalyzerConfigOptions.GetOptions(file).TryGetValue("build_metadata.additionalfiles.CacheObjects", out string? cacheObjectsString); - bool.TryParse(cacheObjectsString, out bool cacheObjects); + options.TryGetValue("build_metadata.additionalfiles.CacheObjects", out string? cacheObjectsString); + bool.TryParse(cacheObjectsString, out bool cacheObjects); - yield return (loadType, cacheObjects, file); - } - } + return (loadType, cacheObjects, source.file); } - public void Execute(GeneratorExecutionContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - IEnumerable<(CsvLoadType, bool, AdditionalText)> options = GetLoadOptions(context); - IEnumerable<(string, string)> nameCodeSequence = SourceFilesFromAdditionalFiles(options); - foreach ((string name, string code) in nameCodeSequence) - context.AddSource($"Csv_{name}", SourceText.From(code, Encoding.UTF8)); - } + IncrementalValuesProvider files + = context.AdditionalTextsProvider.Where( static file => file.Path.EndsWith(".csv")); - public void Initialize(GeneratorInitializationContext context) - { + IncrementalValuesProvider<(AdditionalText, AnalyzerConfigOptionsProvider)> + combined = files.Combine(context.AnalyzerConfigOptionsProvider); + + IncrementalValuesProvider<(CsvLoadType, bool, AdditionalText)> transformed + = combined.Select(GetLoadOptionProvider); + + var selector = static ((CsvLoadType ltype, bool cache, AdditionalText text)source, CancellationToken ctoken) + => SourceFilesFromAdditionalFile(source.ltype, source.cache, source.text); + IncrementalValuesProvider<(string name, string code)> nameAndContent = transformed.SelectMany(selector); + + context.RegisterSourceOutput(nameAndContent, (spc, nameAndContent) => + spc.AddSource($"{nameAndContent.name}.g.cs", nameAndContent.code)); } } From c6d4d1afe3e544ab58f0511ea82607427164411d Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Tue, 26 Apr 2022 15:30:50 +0200 Subject: [PATCH 16/21] Ported the Mustache Generator to the incremental model. --- .../MustacheGenerator.cs | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs index 7a0c7ec15..0363a5dc7 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs @@ -1,7 +1,7 @@ namespace Mustache; [Generator] -public class MustacheGenerator : ISourceGenerator +public class MustacheGenerator : IIncrementalGenerator { private const string attributeSource = @" [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] @@ -15,16 +15,6 @@ public MustacheAttribute(string name, string template, string hash) } "; - public void Execute(GeneratorExecutionContext context) - { - SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!; - foreach ((string name, string template, string hash) in rx.TemplateInfo) - { - string source = SourceFileFromMustachePath(name, template, hash); - context.AddSource($"Mustache{name}", source); - } - } - static string SourceFileFromMustachePath(string name, string template, string hash) { Func tree = HandlebarsDotNet.Handlebars.Compile(template); @@ -44,29 +34,39 @@ public static partial class Constants {{ return sb.ToString(); } - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForPostInitialization((pi) => pi.AddSource("Mustache_MainAttributes__", attributeSource)); - context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); + context.RegisterPostInitializationOutput( + ctx => ctx.AddSource("Mustache_MainAttributes__.g.cs", attributeSource)); + + var maybeTemplateData = context.SyntaxProvider.CreateSyntaxProvider( + predicate: (s, t) => s is AttributeSyntax attrib + && attrib.ArgumentList?.Arguments.Count == 3, + transform: GetTemplateData); + + var templateData = maybeTemplateData + .Where(t => t is not null) + .Select((t, _) => t!.Value); + + context.RegisterSourceOutput(templateData, + (ctx, data) => ctx.AddSource( + $"{data.name}.g.cs", + SourceFileFromMustachePath(data.name, data.template, data.hash))); } - class SyntaxReceiver : ISyntaxContextReceiver + internal static (string name, string template, string hash)? + GetTemplateData(GeneratorSyntaxContext context, CancellationToken cancellationToken) { - public List<(string name, string template, string hash)> TemplateInfo = new (); - - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + var attrib = (AttributeSyntax)context.Node; + + if(attrib.ArgumentList is not null + && context.SemanticModel.GetTypeInfo(attrib).Type?.ToDisplayString() == "MustacheAttribute") { - // find all valid mustache attributes - if (context.Node is AttributeSyntax attrib - && attrib.ArgumentList?.Arguments.Count == 3 - && context.SemanticModel.GetTypeInfo(attrib).Type?.ToDisplayString() == "MustacheAttribute") - { - string name = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[0].Expression).ToString(); - string template = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[1].Expression).ToString(); - string hash = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[2].Expression).ToString(); - - TemplateInfo.Add((name, template, hash)); - } + string name = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[0].Expression).ToString(); + string template = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[1].Expression).ToString(); + string hash = context.SemanticModel.GetConstantValue(attrib.ArgumentList.Arguments[2].Expression).ToString(); + return (name, template, hash); } + return null; } } From e8f928f691297d7b0ffce9c8ee980dbd651e16a9 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 27 Apr 2022 11:26:45 +0200 Subject: [PATCH 17/21] Converted the Maths generator --- .../SourceGeneratorSamples/MathsGenerator.cs | 57 +++++++++---------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs index 538b1bb07..e5fd1da02 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs @@ -393,7 +393,7 @@ private static void Funct(Context ctx) { } [Generator] -public class MathsGenerator : ISourceGenerator +public class MathsGenerator : IIncrementalGenerator { private const string libraryCode = @" using System.Linq; @@ -415,42 +415,41 @@ public static double MySum(int start, int end, Func f) => } "; - public void Execute(GeneratorExecutionContext context) + public (string name, SourceText code) GetNameAndCode(AdditionalText file, CancellationToken _) { - foreach (AdditionalText file in context.AdditionalFiles) - { - if (Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase)) - { - // Load formulas from .math files - var mathText = file.GetText(); - string mathString; - - if (mathText != null) - { - mathString = mathText.ToString(); - } - else - { - throw new Exception($"Cannot load file {file.Path}"); - } + // Load formulas from .math files + var mathText = file.GetText(); + string mathString; - // Get name of generated namespace from file name - string fileName = Path.GetFileNameWithoutExtension(file.Path); + if (mathText != null) + { + mathString = mathText.ToString(); + } + else + { + throw new Exception($"Cannot load file {file.Path}"); + } - // Parse and gen the formulas functions - var tokens = Lexer.Tokenize(mathString); - var code = Parser.Parse(tokens); + // Get name of generated namespace from file name + string fileName = Path.GetFileNameWithoutExtension(file.Path); - var codeFileName = $@"{fileName}.cs"; + // Parse and gen the formulas functions + var tokens = Lexer.Tokenize(mathString); + var code = Parser.Parse(tokens); - context.AddSource(codeFileName, SourceText.From(code, Encoding.UTF8)); - } - } + var codeFileName = $@"{fileName}.cs"; + + return (codeFileName, SourceText.From(code, Encoding.UTF8)); } - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterForPostInitialization((pi) => pi.AddSource("__MathLibrary__.cs", libraryCode)); + context.RegisterPostInitializationOutput((pi) => pi.AddSource("__MathLibrary__.cs", libraryCode)); + + var files = context.AdditionalTextsProvider.Where(file => Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase)); + var nameAndCode = files.Select(GetNameAndCode); + + context.RegisterSourceOutput(nameAndCode, (ctx, source) => ctx.AddSource(source.name, source.code)); } } From e300f6892bbe2f8fab44dee43f682dc8bf2090b7 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 27 Apr 2022 11:40:33 +0200 Subject: [PATCH 18/21] Convert XmlSourceGenerator file --- .../SettingsXmlGenerator.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index 45da3fe62..886ce93a5 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -1,19 +1,9 @@ namespace Analyzer1; [Generator] -public class SettingsXmlGenerator : ISourceGenerator +public class SettingsXmlGenerator : IIncrementalGenerator { - public void Execute(GeneratorExecutionContext context) - { - // Using the context, get any additional files that end in .xmlsettings - IEnumerable settingsFiles = context.AdditionalFiles.Where(at => at.Path.EndsWith(".xmlsettings")); - foreach (AdditionalText settingsFile in settingsFiles) - { - ProcessSettingsFile(settingsFile, context); - } - } - - private void ProcessSettingsFile(AdditionalText xmlFile, GeneratorExecutionContext context) + private void ProcessSettingsFile(SourceProductionContext context, AdditionalText xmlFile) { // try and load the settings file XmlDocument xmlDoc = new (); @@ -85,7 +75,9 @@ public class {name}Settings context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)); } - public void Initialize(GeneratorInitializationContext context) + public void Initialize(IncrementalGeneratorInitializationContext context) { + var xmlFiles = context.AdditionalTextsProvider.Where(at => at.Path.EndsWith(".xmlsettings")); + context.RegisterSourceOutput(xmlFiles, ProcessSettingsFile); } } From f8eed7637d1bba194d9e9d8c59f6b595129b5254 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 27 Apr 2022 13:12:14 +0200 Subject: [PATCH 19/21] Converted the AutoNotifyGenerator project --- .../AutoNotifyGenerator.cs | 100 ++++++++---------- 1 file changed, 42 insertions(+), 58 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index 83c512742..76bc7966a 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -1,9 +1,7 @@ namespace SourceGeneratorSamples; -#nullable disable // TODO: make this generator nullable safe. - [Generator] -public class AutoNotifyGenerator : ISourceGenerator +public class AutoNotifyGenerator : IIncrementalGenerator { private const string attributeText = @" using System; @@ -22,39 +20,10 @@ public AutoNotifyAttribute() "; - public void Initialize(GeneratorInitializationContext context) - { - // Register the attribute source - context.RegisterForPostInitialization((i) => i.AddSource("AutoNotifyAttribute", attributeText)); - - // Register a syntax receiver that will be created for each generation pass - context.RegisterForSyntaxNotifications(() => new SyntaxReceiver()); - } - - public void Execute(GeneratorExecutionContext context) - { - // retrieve the populated receiver - if (context.SyntaxContextReceiver is not SyntaxReceiver receiver) - return; - - // get the added attribute, and INotifyPropertyChanged - INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute"); - INamedTypeSymbol notifySymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); - - // group the fields by class, and generate the source - foreach (IGrouping group in receiver.Fields.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) - { - string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol); - context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); - } - } - - private string ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol) + private string? ProcessClass(INamedTypeSymbol classSymbol, List fields, ISymbol attributeSymbol, ISymbol notifySymbol) { if (!classSymbol.ContainingSymbol.Equals(classSymbol.ContainingNamespace, SymbolEqualityComparer.Default)) - { return null; //TODO: issue a diagnostic that it must be top level - } string namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); @@ -89,7 +58,7 @@ private void ProcessField(StringBuilder source, IFieldSymbol fieldSymbol, ISymbo ITypeSymbol fieldType = fieldSymbol.Type; // get the AutoNotify attribute from the field, and any associated data - AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); + AttributeData attributeData = fieldSymbol.GetAttributes().Single(ad => ad.AttributeClass!.Equals(attributeSymbol, SymbolEqualityComparer.Default)); TypedConstant overridenNameOpt = attributeData.NamedArguments.SingleOrDefault(kvp => kvp.Key == "PropertyName").Value; string propertyName = chooseName(fieldName, overridenNameOpt); @@ -120,7 +89,7 @@ static string chooseName(string fieldName, TypedConstant overridenNameOpt) { if (!overridenNameOpt.IsNull) { - return overridenNameOpt.Value.ToString(); + return overridenNameOpt.Value!.ToString(); } fieldName = fieldName.TrimStart('_'); @@ -135,32 +104,47 @@ static string chooseName(string fieldName, TypedConstant overridenNameOpt) } - /// - /// Created on demand before each generation pass - /// - class SyntaxReceiver : ISyntaxContextReceiver + public void GenerateCode(SourceProductionContext context, (ImmutableArray symbols, Compilation compilation) source) { - public List Fields { get; } = new List(); + // get the added attribute, and INotifyPropertyChanged + INamedTypeSymbol attributeSymbol = source.compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute")!; + INamedTypeSymbol notifySymbol = source.compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged")!; - /// - /// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation - /// - public void OnVisitSyntaxNode(GeneratorSyntaxContext context) + // group the fields by class, and generate the source + foreach (IGrouping group in source.symbols.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) { - // any field with at least one attribute is a candidate for property generation - if (context.Node is FieldDeclarationSyntax fieldDeclarationSyntax - && fieldDeclarationSyntax.AttributeLists.Count > 0) - { - foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables) - { - // Get the symbol being declared by the field, and keep it if its annotated - IFieldSymbol fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable) as IFieldSymbol; - if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.ToDisplayString() == "AutoNotify.AutoNotifyAttribute")) - { - Fields.Add(fieldSymbol); - } - } - } + string? classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol); + if(classSource is not null) + context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); + } + } + public void Initialize(IncrementalGeneratorInitializationContext context) + { + context.RegisterPostInitializationOutput((i) => i.AddSource("AutoNotifyAttribute", attributeText)); + + var fieldsEnum = context.SyntaxProvider.CreateSyntaxProvider( + predicate: (node, ct) => node is FieldDeclarationSyntax fieldDeclarationSyntax + && fieldDeclarationSyntax.AttributeLists.Count > 0, + transform: FieldsTransform); + + var fields = fieldsEnum.SelectMany((en, _) => en); + + var collected = fields.Collect() + .Combine(context.CompilationProvider); + + context.RegisterSourceOutput(collected, GenerateCode); + } + + + IEnumerable FieldsTransform(GeneratorSyntaxContext context, CancellationToken ct) + { + var fieldDeclarationSyntax = (FieldDeclarationSyntax) context.Node; + + foreach (VariableDeclaratorSyntax variable in fieldDeclarationSyntax.Declaration.Variables) + { + IFieldSymbol? fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable) as IFieldSymbol; + if (fieldSymbol!.GetAttributes().Any(ad => ad.AttributeClass!.ToDisplayString() == "AutoNotify.AutoNotifyAttribute")) + yield return fieldSymbol; } } } From c11d525c3cd32c2353c6cdde3dee3163e1c8d78c Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Wed, 27 Apr 2022 13:16:16 +0200 Subject: [PATCH 20/21] Fix typo in Readme. --- samples/CSharp/SourceGenerators/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/CSharp/SourceGenerators/README.md b/samples/CSharp/SourceGenerators/README.md index 958cf03f6..83fdc60ed 100644 --- a/samples/CSharp/SourceGenerators/README.md +++ b/samples/CSharp/SourceGenerators/README.md @@ -3,7 +3,7 @@ These samples are for an in-progress feature of Roslyn. As such they may change or break as the feature is developed, and no level of support is implied. -For more infomation on the Source Generators feature, see the [design document](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.md). +For more information on the Source Generators feature, see the [design document](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.md). Prerequisites ----- From a8d58b1b239ef7a7516b6c5547bab2b27ef35d89 Mon Sep 17 00:00:00 2001 From: Luca Bolognese Date: Fri, 13 May 2022 15:10:35 +0200 Subject: [PATCH 21/21] Make remaining generated files conform to the `.g.cs` standard. --- .../SourceGeneratorSamples/AutoNotifyGenerator.cs | 2 +- .../SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs | 4 ++-- .../SourceGeneratorSamples/SettingsXmlGenerator.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index 76bc7966a..7904ea4d2 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -115,7 +115,7 @@ public void GenerateCode(SourceProductionContext context, (ImmutableArray f) => var tokens = Lexer.Tokenize(mathString); var code = Parser.Parse(tokens); - var codeFileName = $@"{fileName}.cs"; + var codeFileName = $@"{fileName}.g.cs"; return (codeFileName, SourceText.From(code, Encoding.UTF8)); } public void Initialize(IncrementalGeneratorInitializationContext context) { - context.RegisterPostInitializationOutput((pi) => pi.AddSource("__MathLibrary__.cs", libraryCode)); + context.RegisterPostInitializationOutput((pi) => pi.AddSource("__MathLibrary__.g.cs", libraryCode)); var files = context.AdditionalTextsProvider.Where(file => Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase)); var nameAndCode = files.Select(GetNameAndCode); diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index 886ce93a5..d1afee0b6 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -72,7 +72,7 @@ public class {name}Settings sb.Append("} } }"); - context.AddSource($"Settings_{name}", SourceText.From(sb.ToString(), Encoding.UTF8)); + context.AddSource($"Settings_{name}.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8)); } public void Initialize(IncrementalGeneratorInitializationContext context)