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;
- }
-}