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