From 3b9eee50f5143cfa870053132f58d4ea1d25d116 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 23 Feb 2021 19:51:54 -0800 Subject: [PATCH 1/4] Update AutoNotifyGenerator to use new 16.9 APIs --- eng/Versions.props | 2 +- .../AutoNotifyGenerator.cs | 59 ++++++++----------- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 2976df46c..0a32958ae 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -10,7 +10,7 @@ true true true - 3.8.0-4.20464.1 + 3.9.0-4.final 16.1.1 diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs index 5ec21421a..3e2bfa811 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs @@ -17,6 +17,7 @@ public class AutoNotifyGenerator : ISourceGenerator namespace AutoNotify { [AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + [System.Diagnostics.Conditional(""AutoNotifyGenerator_DEBUG"")] sealed class AutoNotifyAttribute : Attribute { public AutoNotifyAttribute() @@ -27,48 +28,28 @@ 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) { - // add the attribute text - context.AddSource("AutoNotifyAttribute", SourceText.From(attributeText, Encoding.UTF8)); - - // retreive the populated receiver - if (!(context.SyntaxReceiver is SyntaxReceiver receiver)) + // retrieve the populated receiver + if (!(context.SyntaxContextReceiver is SyntaxReceiver receiver)) return; - // we're going to create a new compilation that contains the attribute. - // TODO: we should allow source generators to provide source during initialize, so that this step isn't required. - CSharpParseOptions options = (context.Compilation as CSharpCompilation).SyntaxTrees[0].Options as CSharpParseOptions; - Compilation compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(attributeText, Encoding.UTF8), options)); - - // get the newly bound attribute, and INotifyPropertyChanged - INamedTypeSymbol attributeSymbol = compilation.GetTypeByMetadataName("AutoNotify.AutoNotifyAttribute"); - INamedTypeSymbol notifySymbol = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); - - // loop over the candidate fields, and keep the ones that are actually annotated - List fieldSymbols = new List(); - foreach (FieldDeclarationSyntax field in receiver.CandidateFields) - { - SemanticModel model = compilation.GetSemanticModel(field.SyntaxTree); - foreach (VariableDeclaratorSyntax variable in field.Declaration.Variables) - { - // Get the symbol being decleared by the field, and keep it if its annotated - IFieldSymbol fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol; - if (fieldSymbol.GetAttributes().Any(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default))) - { - fieldSymbols.Add(fieldSymbol); - } - } - } + // 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 fieldSymbols.GroupBy(f => f.ContainingType)) + foreach (IGrouping group in receiver.Fields.GroupBy(f => f.ContainingType)) { string classSource = ProcessClass(group.Key, group.ToList(), attributeSymbol, notifySymbol, context); context.AddSource($"{group.Key.Name}_autoNotify.cs", SourceText.From(classSource, Encoding.UTF8)); @@ -164,20 +145,28 @@ string chooseName(string fieldName, TypedConstant overridenNameOpt) /// /// Created on demand before each generation pass /// - class SyntaxReceiver : ISyntaxReceiver + class SyntaxReceiver : ISyntaxContextReceiver { - public List CandidateFields { get; } = new List(); + 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(SyntaxNode syntaxNode) + public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { // any field with at least one attribute is a candidate for property generation - if (syntaxNode is FieldDeclarationSyntax fieldDeclarationSyntax + if (context.Node is FieldDeclarationSyntax fieldDeclarationSyntax && fieldDeclarationSyntax.AttributeLists.Count > 0) { - CandidateFields.Add(fieldDeclarationSyntax); + 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); + } + } } } } From b3048fbc5fdbd599719f1a7af6ecb4db55452f05 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 23 Feb 2021 19:54:12 -0800 Subject: [PATCH 2/4] Use post init in maths generator --- .../SourceGeneratorSamples/MathsGenerator.cs | 16 ++++++---------- .../SettingsXmlGenerator.cs | 9 --------- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs index 4598dca43..7cf2c1676 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MathsGenerator.cs @@ -408,8 +408,6 @@ private static void Funct(Context ctx) { [Generator] public class MathsGenerator : ISourceGenerator { - private bool libraryIsAdded = false; - private const string libraryCode = @" using System.Linq; using System; @@ -432,16 +430,10 @@ public static double MySum(int start, int end, Func f) => public void Execute(GeneratorExecutionContext context) { - foreach (AdditionalText file in context.AdditionalFiles) { if (Path.GetExtension(file.Path).Equals(".math", StringComparison.OrdinalIgnoreCase)) { - if(!libraryIsAdded) - { - context.AddSource("___MathLibrary___.cs", SourceText.From(libraryCode, Encoding.UTF8)); - libraryIsAdded = true; - } // Load formulas from .math files var mathText = file.GetText(); var mathString = ""; @@ -449,7 +441,8 @@ public void Execute(GeneratorExecutionContext context) if(mathText != null) { mathString = mathText.ToString(); - } else + } + else { throw new Exception($"Cannot load file {file.Path}"); } @@ -468,7 +461,10 @@ public void Execute(GeneratorExecutionContext context) } } - public void Initialize(GeneratorInitializationContext context) { } + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForPostInitialization((pi) => pi.AddSource("__MathLibrary__.cs", libraryCode)); + } } } diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs index bbb0a5aa1..7854de5ef 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SettingsXmlGenerator.cs @@ -12,15 +12,6 @@ namespace Analyzer1 [Generator] public class SettingsXmlGenerator : ISourceGenerator { - private const string SettingsFileString = @" -namespace XmlSettings -{ - public partial class XmlSettings - { - - } -} -"; public void Execute(GeneratorExecutionContext context) { // Using the context, get any additional files that end in .xmlsettings From 6e0451568f869f073881c9b82865338f1921fddc Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 23 Feb 2021 19:56:47 -0800 Subject: [PATCH 3/4] Clean up generatedDemo csproj --- .../CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj index 46ae12939..b95bde39c 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/GeneratedDemo.csproj @@ -3,16 +3,13 @@ Exe netcoreapp3.1 - preview - - From 3c02978f3a04ad5c1dc934e3951134398351f36a Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Wed, 24 Feb 2021 08:36:44 -0800 Subject: [PATCH 4/4] Refactor mustache template generator: - Use PostInit - Use syntax context to collect info as we go --- .../GeneratedDemo/UseMustacheGenerator.cs | 1 - .../MustacheGenerator.cs | 102 ++++++------------ 2 files changed, 30 insertions(+), 73 deletions(-) diff --git a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs index a2d1cd778..992d56800 100644 --- a/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs +++ b/samples/CSharp/SourceGenerators/GeneratedDemo/UseMustacheGenerator.cs @@ -16,7 +16,6 @@ namespace GeneratedDemo { class UseMustacheGenerator { - public static void Run() { WriteLine(Mustache.Constants.Lottery); diff --git a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs index 8a514cc78..51766e7c7 100644 --- a/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs +++ b/samples/CSharp/SourceGenerators/SourceGeneratorSamples/MustacheGenerator.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; #nullable enable @@ -15,9 +12,7 @@ namespace Mustache [Generator] public class MustacheGenerator : ISourceGenerator { - public void Execute(GeneratorExecutionContext context) - { - string attributeSource = @" + private const string attributeSource = @" [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple=true)] internal sealed class MustacheAttribute: System.Attribute { @@ -28,97 +23,60 @@ public MustacheAttribute(string name, string template, string hash) => (Name, Template, Hash) = (name, template, hash); } "; - context.AddSource("Mustache_MainAttributes__", SourceText.From(attributeSource, Encoding.UTF8)); - - Compilation compilation = context.Compilation; - IEnumerable<(string, string, string)> options = GetMustacheOptions(compilation); - IEnumerable<(string, string)> namesSources = SourceFilesFromMustachePaths(options); - - foreach ((string name, string source) in namesSources) - { - context.AddSource($"Mustache{name}", SourceText.From(source, Encoding.UTF8)); - } - } - - static IEnumerable<(string, string, string)> GetMustacheOptions(Compilation compilation) + public void Execute(GeneratorExecutionContext context) { - // Get all Mustache attributes - IEnumerable? allNodes = compilation.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes()); - IEnumerable allAttributes = allNodes.Where((d) => d.IsKind(SyntaxKind.Attribute)).OfType(); - ImmutableArray attributes = allAttributes.Where(d => d.Name.ToString() == "Mustache") - .ToImmutableArray(); - - IEnumerable models = compilation.SyntaxTrees.Select(st => compilation.GetSemanticModel(st)); - foreach (AttributeSyntax att in attributes) + SyntaxReceiver rx = (SyntaxReceiver)context.SyntaxContextReceiver!; + foreach ((string name, string template, string hash) in rx.TemplateInfo) { - string mustacheName = "", template = "", hash = ""; - int index = 0; - - if (att.ArgumentList is null) throw new Exception("Can't be null here"); - - SemanticModel m = compilation.GetSemanticModel(att.SyntaxTree); - - foreach (AttributeArgumentSyntax arg in att.ArgumentList.Arguments) - { - ExpressionSyntax expr = arg.Expression; - - TypeInfo t = m.GetTypeInfo(expr); - Optional v = m.GetConstantValue(expr); - if (index == 0) - { - mustacheName = v.ToString(); - } - else if (index == 1) - { - template = v.ToString(); - } - else - { - hash = v.ToString(); - } - index += 1; - } - yield return (mustacheName, template, hash); + 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); - return GenerateMustacheClass(name, mustacheText); - } - - static IEnumerable<(string, string)> SourceFilesFromMustachePaths(IEnumerable<(string, string, string)> pathsData) - { - - foreach ((string name, string template, string hash) in pathsData) - { - yield return (name, SourceFileFromMustachePath(name, template, hash)); - } - } - - private static string GenerateMustacheClass(string className, string mustacheText) - { StringBuilder sb = new StringBuilder(); sb.Append($@" namespace Mustache {{ public static partial class Constants {{ - public const string {className} = @""{mustacheText.Replace("\"", "\"\"")}""; + public const string {name} = @""{mustacheText.Replace("\"", "\"\"")}""; }} }} "); return sb.ToString(); - } public void Initialize(GeneratorInitializationContext context) { - // No initialization required + 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)>(); + + 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") + { + 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)); + } + } } } }