File: ContextualOptionsGenerator.cs
Web Access
Project: src\src\Generators\Microsoft.Gen.ContextualOptions\Microsoft.Gen.ContextualOptions.csproj (Microsoft.Gen.ContextualOptions)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Gen.ContextualOptions.Model;
 
namespace Microsoft.Gen.ContextualOptions;
 
[Generator]
public class ContextualOptionsGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        IncrementalValuesProvider<SyntaxNode> typeDeclarations = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "Microsoft.Extensions.Options.Contextual.OptionsContextAttribute",
                (_, _) => true,
                (context, _) => context.TargetNode);
 
        IncrementalValueProvider<(Compilation, ImmutableArray<SyntaxNode>)> compilationAndTypes =
            context.CompilationProvider.Combine(typeDeclarations.Collect());
 
        context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => HandleAnnotatedTypes(source.Item1, source.Item2, spc));
    }
 
    private static void HandleAnnotatedTypes(Compilation compilation, IEnumerable<SyntaxNode> nodes, SourceProductionContext context)
    {
        if (!SymbolLoader.TryLoad(compilation, out var holder))
        {
            return;
        }
 
        var typeDeclarations = nodes.OfType<TypeDeclarationSyntax>()
            .ToLookup(declaration => declaration.SyntaxTree)
            .SelectMany(declarations => declarations.Select(declaration => (symbol: compilation.GetSemanticModel(declarations.Key).GetDeclaredSymbol(declaration), declaration)))
            .Where(_ => _.symbol is INamedTypeSymbol)
            .Where(_ => _.symbol!.GetAttributes().Any(attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, holder!.OptionsContextAttribute)))
            .ToLookup(_ => _.symbol, _ => _.declaration, comparer: SymbolEqualityComparer.Default)
            .ToDictionary<IGrouping<ISymbol?, TypeDeclarationSyntax>, INamedTypeSymbol, List<TypeDeclarationSyntax>>(
                group => (INamedTypeSymbol)group.Key!, group => group.ToList(), comparer: SymbolEqualityComparer.Default);
 
        var list = new List<OptionsContextType>();
        foreach (var type in Parser.GetContextualOptionTypes(typeDeclarations))
        {
            context.CancellationToken.ThrowIfCancellationRequested();
            type.Diagnostics.ForEach(context.ReportDiagnostic);
 
            if (type.ShouldEmit)
            {
                list.Add(type);
            }
        }
 
        if (list.Count > 0)
        {
            var emitter = new Emitter();
            context.AddSource($"ContextualOptions.g.cs", emitter.Emit(list.OrderBy(x => x.Namespace + "." + x.Name)));
        }
    }
}