From f4c0e4e930db7a2c8ef8ab8cd386462109677026 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 21 Dec 2025 11:18:07 +0000
Subject: [PATCH 1/2] Initial plan
From 32933aa1651dcffdc02025852d622dbe33a44c31 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 21 Dec 2025 11:23:27 +0000
Subject: [PATCH 2/2] Convert MetricsSourceGenerator to IIncrementalGenerator
and remove RS1035/RS1042 suppressions
Co-authored-by: einari <134365+einari@users.noreply.github.com>
---
.globalconfig | 2 -
.../Metrics.Roslyn/MetricsSourceGenerator.cs | 86 ++++++++++++-------
.../Metrics.Roslyn/MetricsSyntaxReceiver.cs | 49 -----------
3 files changed, 57 insertions(+), 80 deletions(-)
delete mode 100644 Source/DotNET/Metrics.Roslyn/MetricsSyntaxReceiver.cs
diff --git a/.globalconfig b/.globalconfig
index 12ebac79..e44814ff 100644
--- a/.globalconfig
+++ b/.globalconfig
@@ -99,8 +99,6 @@ dotnet_diagnostic.SA1601.severity = none
dotnet_diagnostic.SA1623.severity = none
dotnet_diagnostic.SA1633.severity = none
dotnet_diagnostic.SA1642.severity = none
-dotnet_diagnostic.RS1035.severity = none
-dotnet_diagnostic.RS1042.severity = none
dotnet_diagnostic.RCS1018.severity = none
dotnet_diagnostic.RCS1057.severity = warning
dotnet_diagnostic.RCS1079.severity = none
diff --git a/Source/DotNET/Metrics.Roslyn/MetricsSourceGenerator.cs b/Source/DotNET/Metrics.Roslyn/MetricsSourceGenerator.cs
index ce224a8b..0fff0cd8 100644
--- a/Source/DotNET/Metrics.Roslyn/MetricsSourceGenerator.cs
+++ b/Source/DotNET/Metrics.Roslyn/MetricsSourceGenerator.cs
@@ -2,9 +2,9 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Immutable;
-using System.Reflection;
using Cratis.Metrics.Roslyn.Templates;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Cratis.Metrics.Roslyn;
@@ -13,7 +13,7 @@ namespace Cratis.Metrics.Roslyn;
/// Represents the source generator for metrics.
///
[Generator]
-public class MetricsSourceGenerator : ISourceGenerator
+public class MetricsSourceGenerator : IIncrementalGenerator
{
static readonly string[] _systemUsings =
[
@@ -21,29 +21,63 @@ public class MetricsSourceGenerator : ISourceGenerator
"System.Diagnostics.Metrics"
];
- static MetricsSourceGenerator()
+ ///
+ public void Initialize(IncrementalGeneratorInitializationContext context)
{
- AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
- {
- if (args.Name.StartsWith("Handlebars"))
- {
- const string path = "/Volumes/Code/Cratis/Fundamentals/Source/DotNET/Metrics.Roslyn/bin/Debug/netstandard2.0/Handlebars.dll";
- return File.Exists(path) ? Assembly.LoadFile(path) : null;
- }
- return null;
- };
+ // Filter to partial static classes with partial static methods that have metrics attributes
+ var candidateClasses = context.SyntaxProvider
+ .CreateSyntaxProvider(
+ predicate: static (node, _) => IsCandidateClass(node),
+ transform: static (ctx, _) => GetClassDeclaration(ctx))
+ .Where(static candidate => candidate is not null);
+
+ // Combine with compilation to get semantic model
+ var compilationAndClasses = context.CompilationProvider.Combine(candidateClasses.Collect());
+
+ // Generate source for each class
+ context.RegisterSourceOutput(compilationAndClasses, static (spc, source) => Execute(spc, source.Left, source.Right!));
}
- ///
- public void Execute(GeneratorExecutionContext context)
+ static bool IsCandidateClass(SyntaxNode node)
+ {
+ if (node is not ClassDeclarationSyntax classSyntax) return false;
+
+ if (!classSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)) return false;
+ if (!classSyntax.Modifiers.Any(SyntaxKind.StaticKeyword)) return false;
+
+ return classSyntax.Members.Any(member =>
+ member is MethodDeclarationSyntax method &&
+ method.Modifiers.Any(SyntaxKind.PartialKeyword) &&
+ method.Modifiers.Any(SyntaxKind.StaticKeyword) &&
+ HasMetricsAttribute(method));
+ }
+
+ static bool HasMetricsAttribute(MethodDeclarationSyntax method)
{
- if (context.SyntaxReceiver is not MetricsSyntaxReceiver receiver) return;
+ var metricsAttributes = new[] { "Counter", "Gauge" };
+ return method.AttributeLists
+ .SelectMany(list => list.Attributes)
+ .Any(attr => metricsAttributes.Any(m => attr.Name.ToString().StartsWith(m)));
+ }
+
+ static ClassDeclarationSyntax? GetClassDeclaration(GeneratorSyntaxContext context)
+ {
+ return context.Node as ClassDeclarationSyntax;
+ }
+
+ static void Execute(SourceProductionContext context, Compilation compilation, ImmutableArray candidates)
+ {
+ if (candidates.IsDefaultOrEmpty) return;
- var counterAttribute = context.Compilation.GetTypeByMetadataName("Cratis.Metrics.CounterAttribute`1")!;
- var gaugeAttribute = context.Compilation.GetTypeByMetadataName("Cratis.Metrics.GaugeAttribute`1")!;
+ var counterAttribute = compilation.GetTypeByMetadataName("Cratis.Metrics.CounterAttribute`1");
+ var gaugeAttribute = compilation.GetTypeByMetadataName("Cratis.Metrics.GaugeAttribute`1");
- foreach (var candidate in receiver.Candidates)
+ if (counterAttribute is null || gaugeAttribute is null) return;
+
+ foreach (var candidate in candidates)
{
+ if (candidate is null) continue;
+
var classDefinition = $"{candidate.Modifiers} class {candidate.Identifier.ValueText}";
var usings = GetUsingsFor(candidate);
@@ -55,7 +89,7 @@ public void Execute(GeneratorExecutionContext context)
UsingStatements = [.. usings]
};
- var semanticModel = context.Compilation.GetSemanticModel(candidate.SyntaxTree);
+ var semanticModel = compilation.GetSemanticModel(candidate.SyntaxTree);
foreach (var member in candidate.Members)
{
if (member is not MethodDeclarationSyntax method) continue;
@@ -87,12 +121,6 @@ public void Execute(GeneratorExecutionContext context)
}
}
- ///
- public void Initialize(GeneratorInitializationContext context)
- {
- context.RegisterForSyntaxNotifications(() => new MetricsSyntaxReceiver());
- }
-
static IEnumerable GetUsingsFor(ClassDeclarationSyntax candidate)
{
var current = candidate.Parent;
@@ -139,7 +167,7 @@ static IEnumerable GetParametersAsTags(IEnumerable metrics,
INamedTypeSymbol attributeToLookFor,
MethodDeclarationSyntax method,
diff --git a/Source/DotNET/Metrics.Roslyn/MetricsSyntaxReceiver.cs b/Source/DotNET/Metrics.Roslyn/MetricsSyntaxReceiver.cs
deleted file mode 100644
index 413150f8..00000000
--- a/Source/DotNET/Metrics.Roslyn/MetricsSyntaxReceiver.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) Cratis. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace Cratis.Metrics.Roslyn;
-
-///
-/// Represents the syntax receiver for metrics.
-///
-public class MetricsSyntaxReceiver : ISyntaxReceiver
-{
- static readonly string[] _metricsAttributes =
- [
- "Counter",
- "Gauge"
- ];
-
- readonly List _candidates = [];
-
- ///
- /// Gets the candidates for code generation.
- ///
- internal IEnumerable Candidates => _candidates;
-
- ///
- public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
- {
- if (syntaxNode is not ClassDeclarationSyntax classSyntax) return;
-
- if (classSyntax.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PartialKeyword)) &&
- classSyntax.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.StaticKeyword)) &&
- classSyntax.Members.Any(member => member.IsKind(SyntaxKind.MethodDeclaration) &&
- HasMetricsAttribute(member) &&
- member.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PartialKeyword)) &&
- member.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.StaticKeyword))))
- {
- _candidates.Add(classSyntax);
- }
- }
-
- bool HasMetricsAttribute(MemberDeclarationSyntax memberSyntax) => memberSyntax
- .AttributeLists
- .SelectMany(_ => _.Attributes)
- .Any(_ => _metricsAttributes
- .Any(m => _.Name.ToString().StartsWith(m)));
-}