Created
August 14, 2021 02:52
-
-
Save felipebaltazar/e3096ede8c6c7ca0926380e2011dea53 to your computer and use it in GitHub Desktop.
SourceGenerators
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<TargetFramework>netstandard2.0</TargetFramework> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0-3.final" PrivateAssets="all" /> | |
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.0.0" PrivateAssets="all" /> | |
</ItemGroup> | |
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.CSharp.Syntax; | |
using Microsoft.CodeAnalysis.Text; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
namespace FeatureFlag.Management.Firebase | |
{ | |
[Generator] | |
public class RemoteConfigDefaultValueGenerator : ISourceGenerator | |
{ | |
public void Initialize(GeneratorInitializationContext context) | |
{ | |
// 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.SyntaxReceiver is SyntaxReceiver receiver)) | |
return; | |
receiver.SetSemanticModelGetter(a => context.Compilation.GetSemanticModel(a)); | |
string classSource = ProcessClass(receiver.Properties, context); | |
context.AddSource("RemoteConfigDefaultValues.cs", SourceText.From(classSource, Encoding.UTF8)); | |
} | |
private string ProcessClass(List<IPropertySymbol> properties, GeneratorExecutionContext context) | |
{ | |
var attributeSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DefaultValueAttribute"); | |
StringBuilder source = new StringBuilder($@" | |
namespace FeaturueFlag.Management.Firebase | |
{{ | |
public static class RemoteConfig | |
{{ | |
public static Dictionary<string, object> DefaultValues = new Dictionary<string, object>() | |
{{"); | |
var isFirst = true; | |
foreach (var property in properties) | |
{ | |
source.Append($@" {(isFirst ? string.Empty: ",")}{{ {property.Name}, {GetDefaultValue(property, attributeSymbol)} }}"); | |
isFirst = false; | |
} | |
source.Append(@" }} | |
}; | |
}} | |
}} | |
"); | |
return source.ToString(); | |
} | |
private string GetDefaultValue(IPropertySymbol property, ISymbol attributeSymbol) | |
{ | |
var attributeData = property.GetAttributes().FirstOrDefault(ad => ad.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default)); | |
var overridenValueOpt = attributeData?.NamedArguments.FirstOrDefault(kvp => kvp.Key == "Value").Value; | |
if (overridenValueOpt.HasValue && !overridenValueOpt.Value.IsNull) | |
{ | |
overridenValueOpt.Value.Value.ToString(); | |
} | |
return "default"; | |
} | |
/// <summary> | |
/// Created on demand before each generation pass | |
/// </summary> | |
class SyntaxReceiver : ISyntaxReceiver | |
{ | |
public List<IPropertySymbol> Properties { get; } = new List<IPropertySymbol>(); | |
private Func<SyntaxTree, SemanticModel> _semanticModelGetter; | |
/// <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 context) | |
{ | |
if (!(context is ClassDeclarationSyntax classDeclarationSyntax)) | |
return; | |
var semanticModel = _semanticModelGetter?.Invoke(classDeclarationSyntax.SyntaxTree); | |
if (semanticModel is null) | |
return; | |
if (!(semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is INamedTypeSymbol namedSymbol)) | |
return; | |
if (!InheritsFrom(namedSymbol, "FeatureFlag.Management.IFeature")) | |
return; | |
foreach (var member in classDeclarationSyntax.Members) | |
{ | |
if (!(member is PropertyDeclarationSyntax p)) | |
continue; | |
if (semanticModel.GetDeclaredSymbol(p) is IPropertySymbol propertySymbol) | |
Properties.Add(propertySymbol); | |
} | |
} | |
public void SetSemanticModelGetter(Func<SyntaxTree, SemanticModel> p) | |
{ | |
_semanticModelGetter = p; | |
} | |
private bool InheritsFrom(INamedTypeSymbol symbol, string fullnameType) | |
{ | |
while (true) | |
{ | |
if (symbol.ToString() == fullnameType) | |
return true; | |
if (symbol.BaseType != null) | |
{ | |
symbol = symbol.BaseType; | |
continue; | |
} | |
break; | |
} | |
return false; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment