diff --git a/src/Generators/Microsoft.Gen.ContextualOptions/ContextReceiver.cs b/src/Generators/Microsoft.Gen.ContextualOptions/ContextReceiver.cs deleted file mode 100644 index ccf837bc065..00000000000 --- a/src/Generators/Microsoft.Gen.ContextualOptions/ContextReceiver.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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.Linq; -using System.Threading; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Microsoft.Gen.ContextualOptions; - -/// -/// Type declaration syntax receiver for generators. -/// -internal sealed class ContextReceiver : ISyntaxReceiver -{ - private readonly CancellationToken _token; - - public ContextReceiver(CancellationToken token) - { - _token = token; - } - - private readonly List _typeDeclarations = []; - - public void OnVisitSyntaxNode(SyntaxNode syntaxNode) - { - _token.ThrowIfCancellationRequested(); - - if (syntaxNode is TypeDeclarationSyntax type - && type is not InterfaceDeclarationSyntax) - { - _typeDeclarations.Add(type); - } - } - - public bool TryGetTypeDeclarations(Compilation compilation, out Dictionary>? typeDeclarations) - { - if (!SymbolLoader.TryLoad(compilation, out var holder)) - { - typeDeclarations = default; - return false; - } - - typeDeclarations = _typeDeclarations - .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, INamedTypeSymbol, List>( - group => (INamedTypeSymbol)group.Key!, group => group.ToList(), comparer: SymbolEqualityComparer.Default); - - return true; - } -} diff --git a/src/Generators/Microsoft.Gen.ContextualOptions/ContextualOptionsGenerator.cs b/src/Generators/Microsoft.Gen.ContextualOptions/ContextualOptionsGenerator.cs index e76a50781b8..46a47db4674 100644 --- a/src/Generators/Microsoft.Gen.ContextualOptions/ContextualOptionsGenerator.cs +++ b/src/Generators/Microsoft.Gen.ContextualOptions/ContextualOptionsGenerator.cs @@ -1,7 +1,6 @@ // 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; @@ -15,50 +14,31 @@ public class ContextualOptionsGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { - IncrementalValuesProvider typeDeclarations = context.SyntaxProvider + IncrementalValuesProvider types = context.SyntaxProvider .ForAttributeWithMetadataName( "Microsoft.Extensions.Options.Contextual.OptionsContextAttribute", - (_, _) => true, - (context, _) => context.TargetNode); + (node, _) => node is TypeDeclarationSyntax, + (context, _) => CreateOptionsContextType(context)); - IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndTypes = - context.CompilationProvider.Combine(typeDeclarations.Collect()); + IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndTypes = + context.CompilationProvider.Combine(types.Collect()); - context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => HandleAnnotatedTypes(source.Item1, source.Item2, spc)); + context.RegisterSourceOutput(types.Collect(), static (spc, source) => HandleAnnotatedTypes(source, spc)); } - private static void HandleAnnotatedTypes(Compilation compilation, IEnumerable nodes, SourceProductionContext context) + private static OptionsContextType CreateOptionsContextType(GeneratorAttributeSyntaxContext context) { - if (!SymbolLoader.TryLoad(compilation, out var holder)) - { - return; - } - - var typeDeclarations = nodes.OfType() - .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, INamedTypeSymbol, List>( - group => (INamedTypeSymbol)group.Key!, group => group.ToList(), comparer: SymbolEqualityComparer.Default); - - var list = new List(); - foreach (var type in Parser.GetContextualOptionTypes(typeDeclarations)) - { - context.CancellationToken.ThrowIfCancellationRequested(); - type.Diagnostics.ForEach(context.ReportDiagnostic); - - if (type.ShouldEmit) - { - list.Add(type); - } - } + var symbol = (INamedTypeSymbol)context.TargetSymbol; + var node = context.TargetNode; + return Parser.GetContextualOptionTypes(symbol, (TypeDeclarationSyntax)node); + } - if (list.Count > 0) + private static void HandleAnnotatedTypes(ImmutableArray types, SourceProductionContext context) + { + if (types.Length > 0) { var emitter = new Emitter(); - context.AddSource($"ContextualOptions.g.cs", emitter.Emit(list.OrderBy(x => x.Namespace + "." + x.Name))); + context.AddSource($"ContextualOptions.g.cs", emitter.Emit(types.OrderBy(x => x.Namespace + "." + x.Name))); } } } diff --git a/src/Generators/Microsoft.Gen.ContextualOptions/Model/OptionsContextType.cs b/src/Generators/Microsoft.Gen.ContextualOptions/Model/OptionsContextType.cs index ae99413327d..397caede29b 100644 --- a/src/Generators/Microsoft.Gen.ContextualOptions/Model/OptionsContextType.cs +++ b/src/Generators/Microsoft.Gen.ContextualOptions/Model/OptionsContextType.cs @@ -1,34 +1,30 @@ // 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 Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.Gen.ContextualOptions.Model; +// TODO: Equality internal sealed class OptionsContextType { - public readonly List Diagnostics = []; - public readonly INamedTypeSymbol Symbol; - public readonly ImmutableArray Definitions; public readonly ImmutableArray OptionsContextProperties; - public string Keyword => Definitions[0].Keyword.Text; - public string? Namespace => Symbol.ContainingNamespace.IsGlobalNamespace ? null : Symbol.ContainingNamespace.ToString(); - public string Name => Symbol.Name; - - public bool ShouldEmit => Diagnostics.TrueForAll(diag => diag.Severity != DiagnosticSeverity.Error); - - public string HintName => $"{Namespace}.{Name}"; + public string Keyword; + public string? Namespace; + public string Name; public OptionsContextType( INamedTypeSymbol symbol, - ImmutableArray definitions, + TypeDeclarationSyntax typeDeclarationSyntax, ImmutableArray optionsContextProperties) { - Symbol = symbol; - Definitions = definitions; + // NOTE: NEVER store INamedTypeSymbol in OptionsContextType. + // This is better for source generator incrementality. + Name = symbol.Name; + Namespace = symbol.ContainingNamespace.IsGlobalNamespace ? null : symbol.ContainingNamespace.ToString(); + Keyword = typeDeclarationSyntax.Keyword.Text; OptionsContextProperties = optionsContextProperties; } } diff --git a/src/Generators/Microsoft.Gen.ContextualOptions/Parser.cs b/src/Generators/Microsoft.Gen.ContextualOptions/Parser.cs index ba4fad55578..360bd75781e 100644 --- a/src/Generators/Microsoft.Gen.ContextualOptions/Parser.cs +++ b/src/Generators/Microsoft.Gen.ContextualOptions/Parser.cs @@ -1,11 +1,9 @@ // 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; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Gen.ContextualOptions.Model; @@ -13,63 +11,62 @@ namespace Microsoft.Gen.ContextualOptions; internal static class Parser { - public static IEnumerable GetContextualOptionTypes(Dictionary> types) => - types - .Select(type => new OptionsContextType(type.Key, type.Value.ToImmutableArray(), GetContextProperties(type.Key))) - .Select(CheckInstantiable) - .Select(CheckPartial) - .Select(CheckRefLikeType) - .Select(CheckHasProperties); + public static OptionsContextType GetContextualOptionTypes(INamedTypeSymbol symbol, TypeDeclarationSyntax typeDeclarationSyntax) + => new OptionsContextType(symbol, typeDeclarationSyntax, GetContextProperties(symbol)); - private static OptionsContextType CheckInstantiable(OptionsContextType type) - { - if (type.Symbol.IsStatic) - { - type.Diagnostics.AddRange( - type.Definitions - .SelectMany(def => def.Modifiers) - .Where(modifier => modifier.IsKind(SyntaxKind.StaticKeyword)) - .Select(modifier => Diagnostic.Create(DiagDescriptors.ContextCannotBeStatic, modifier.GetLocation(), type.Name))); - } + // TODO: Separate analyzer for diagnostics. + //private static OptionsContextType CheckInstantiable(OptionsContextType type) + //{ + // if (type.Symbol.IsStatic) + // { + // type.Diagnostics.AddRange( + // type.Definitions + // .SelectMany(def => def.Modifiers) + // .Where(modifier => modifier.IsKind(SyntaxKind.StaticKeyword)) + // .Select(modifier => Diagnostic.Create(DiagDescriptors.ContextCannotBeStatic, modifier.GetLocation(), type.Name))); + // } - return type; - } + // return type; + //} - private static OptionsContextType CheckRefLikeType(OptionsContextType type) - { - if (type.Symbol.IsRefLikeType) - { - type.Diagnostics.AddRange( - type.Definitions - .SelectMany(def => def.Modifiers) - .Where(modifier => modifier.IsKind(SyntaxKind.RefKeyword)) - .Select(modifier => Diagnostic.Create(DiagDescriptors.ContextCannotBeRefLike, modifier.GetLocation(), type.Name))); - } + // TODO: Separate analyzer for diagnostics. + //private static OptionsContextType CheckRefLikeType(OptionsContextType type) + //{ + // if (type.Symbol.IsRefLikeType) + // { + // type.Diagnostics.AddRange( + // type.Definitions + // .SelectMany(def => def.Modifiers) + // .Where(modifier => modifier.IsKind(SyntaxKind.RefKeyword)) + // .Select(modifier => Diagnostic.Create(DiagDescriptors.ContextCannotBeRefLike, modifier.GetLocation(), type.Name))); + // } - return type; - } + // return type; + //} - private static OptionsContextType CheckPartial(OptionsContextType type) - { - if (!type.Definitions.Any(def => def.Modifiers.Any(static token => token.IsKind(SyntaxKind.PartialKeyword)))) - { - type.Diagnostics.AddRange( - type.Definitions.Select(def => Diagnostic.Create(DiagDescriptors.ContextMustBePartial, def.Identifier.GetLocation(), type.Name))); - } + // TODO: Separate analyzer for diagnostics. + //private static OptionsContextType CheckPartial(OptionsContextType type) + //{ + // if (!type.Definitions.Any(def => def.Modifiers.Any(static token => token.IsKind(SyntaxKind.PartialKeyword)))) + // { + // type.Diagnostics.AddRange( + // type.Definitions.Select(def => Diagnostic.Create(DiagDescriptors.ContextMustBePartial, def.Identifier.GetLocation(), type.Name))); + // } - return type; - } + // return type; + //} - private static OptionsContextType CheckHasProperties(OptionsContextType type) - { - if (type.OptionsContextProperties.IsEmpty) - { - type.Diagnostics.AddRange( - type.Definitions.Select(def => Diagnostic.Create(DiagDescriptors.ContextDoesNotHaveValidProperties, def.Identifier.GetLocation(), type.Name))); - } + // TODO: Separate analyzer for diagnostics. + //private static OptionsContextType CheckHasProperties(OptionsContextType type) + //{ + // if (type.OptionsContextProperties.IsEmpty) + // { + // type.Diagnostics.AddRange( + // type.Definitions.Select(def => Diagnostic.Create(DiagDescriptors.ContextDoesNotHaveValidProperties, def.Identifier.GetLocation(), type.Name))); + // } - return type; - } + // return type; + //} private static ImmutableArray GetContextProperties(INamedTypeSymbol symbol) { diff --git a/src/Generators/Microsoft.Gen.ContextualOptions/SymbolHolder.cs b/src/Generators/Microsoft.Gen.ContextualOptions/SymbolHolder.cs deleted file mode 100644 index 28ed33e839b..00000000000 --- a/src/Generators/Microsoft.Gen.ContextualOptions/SymbolHolder.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using Microsoft.CodeAnalysis; - -namespace Microsoft.Gen.ContextualOptions; - -[ExcludeFromCodeCoverage] -internal sealed record class SymbolHolder(INamedTypeSymbol OptionsContextAttribute); diff --git a/src/Generators/Microsoft.Gen.ContextualOptions/SymbolLoader.cs b/src/Generators/Microsoft.Gen.ContextualOptions/SymbolLoader.cs deleted file mode 100644 index 5f9c8d66012..00000000000 --- a/src/Generators/Microsoft.Gen.ContextualOptions/SymbolLoader.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.CodeAnalysis; - -namespace Microsoft.Gen.ContextualOptions; - -internal static class SymbolLoader -{ - public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHolder) - { - symbolHolder = default; - - var optionsContextAttribute = compilation.GetTypeByMetadataName("Microsoft.Extensions.Options.Contextual.OptionsContextAttribute"); - if (optionsContextAttribute is null) - { - return false; - } - - symbolHolder = new SymbolHolder(optionsContextAttribute); - return true; - } -}