Skip to content

Commit

Permalink
Samples updates (#739)
Browse files Browse the repository at this point in the history
* Update AutoNotifyGenerator to use new 16.9 APIs

* Use post init in maths generator

* Clean up generatedDemo csproj

* Refactor mustache template generator:
- Use PostInit
- Use syntax context to collect info as we go
  • Loading branch information
chsienki authored Feb 25, 2021
1 parent 425dcad commit 22a55bf
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 131 deletions.
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<UsingToolNetFrameworkReferenceAssemblies>true</UsingToolNetFrameworkReferenceAssemblies>
<UsingToolMicrosoftNetCompilers>true</UsingToolMicrosoftNetCompilers>
<UsingToolSymbolUploader>true</UsingToolSymbolUploader>
<MicrosoftNetCompilersToolsetVersion>3.8.0-4.20464.1</MicrosoftNetCompilersToolsetVersion>
<MicrosoftNetCompilersToolsetVersion>3.9.0-4.final</MicrosoftNetCompilersToolsetVersion>
<!-- Force prior version due to https://github.com/microsoft/vstest/pull/2192 and https://github.com/microsoft/vstest/pull/2067 -->
<MicrosoftNETTestSdkVersion>16.1.1</MicrosoftNETTestSdkVersion>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<AdditionalFiles Include="People.csv" CsvLoadType="Startup" />
<AdditionalFiles Include="Cars.csv" CsvLoadType="OnDemand" CacheObjects="true" />
<AdditionalFiles Include="Geometry.math" />

<AdditionalFiles Include="MainSettings.xmlsettings" CopyToOutputDirectory="PreserveNewest" />
<None Include="MainSettings.xmlsettings" CopyToOutputDirectory="PreserveNewest" /> <!-- TODO: remove this when AdditionalFiles supports CopyToOutputDirectory -->
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ namespace GeneratedDemo
{
class UseMustacheGenerator
{

public static void Run()
{
WriteLine(Mustache.Constants.Lottery);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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<IFieldSymbol> fieldSymbols = new List<IFieldSymbol>();
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<INamedTypeSymbol, IFieldSymbol> group in fieldSymbols.GroupBy(f => f.ContainingType))
foreach (IGrouping<INamedTypeSymbol, IFieldSymbol> 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));
Expand Down Expand Up @@ -164,20 +145,28 @@ string chooseName(string fieldName, TypedConstant overridenNameOpt)
/// <summary>
/// Created on demand before each generation pass
/// </summary>
class SyntaxReceiver : ISyntaxReceiver
class SyntaxReceiver : ISyntaxContextReceiver
{
public List<FieldDeclarationSyntax> CandidateFields { get; } = new List<FieldDeclarationSyntax>();
public List<IFieldSymbol> Fields { get; } = new List<IFieldSymbol>();

/// <summary>
/// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
/// </summary>
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);
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -432,24 +430,19 @@ public static double MySum(int start, int end, Func<double, double> 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 = "";

if(mathText != null)
{
mathString = mathText.ToString();
} else
}
else
{
throw new Exception($"Cannot load file {file.Path}");
}
Expand All @@ -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));
}
}
}

Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
{
Expand All @@ -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<SyntaxNode>? allNodes = compilation.SyntaxTrees.SelectMany(s => s.GetRoot().DescendantNodes());
IEnumerable<AttributeSyntax> allAttributes = allNodes.Where((d) => d.IsKind(SyntaxKind.Attribute)).OfType<AttributeSyntax>();
ImmutableArray<AttributeSyntax> attributes = allAttributes.Where(d => d.Name.ToString() == "Mustache")
.ToImmutableArray();

IEnumerable<SemanticModel> 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<object?> 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<object, string> 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));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 22a55bf

Please sign in to comment.