From 2d7e4eb890239348d5a700cc579afdd203c9761e Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Wed, 2 Sep 2026 11:42:44 +0100 Subject: [PATCH 1/3] test: added net48 test --- Directory.Packages.props | 2 +- src/Directory.Build.props | 1 + .../MarkerAttributeTemplateEmitter.cs | 1286 ++++++++++++++ .../Generators/TelemetrySourceGenerator.cs | 6 +- .../SourceGenerator/SourceGenerator.csproj | 15 +- .../Templates/SourceEmitter.cs | 1484 ----------------- .../SourceGenerator/Templates/TemplateInfo.cs | 27 +- .../TelemetrySourceGeneratorCachingTests.cs | 17 +- .../TelemetrySourceGeneratorMetricsTests.cs | 1 + .../Infra/SkipOnNetFrameworkAttribute.cs | 17 + .../Infra/TestHelpers.cs | 46 +- .../Polyfills/Net48Polyfills.cs | 26 + .../Refactorings/CodeRefactoringTestBase.cs | 49 +- ...lemetryRefactoringProviderSnapshotTests.cs | 3 + ...ricsToTelemetryRefactoringProviderTests.cs | 3 + .../SourceGenerator.IntegrationTests.csproj | 33 +- .../TelemetrySourceGeneratorCachingTests.cs | 34 +- 17 files changed, 1477 insertions(+), 1573 deletions(-) create mode 100644 src/src/SourceGenerator/Emitters/MarkerAttributeTemplateEmitter.cs delete mode 100644 src/src/SourceGenerator/Templates/SourceEmitter.cs create mode 100644 src/tests/SourceGenerator.IntegrationTests/Infra/SkipOnNetFrameworkAttribute.cs create mode 100644 src/tests/SourceGenerator.IntegrationTests/Polyfills/Net48Polyfills.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index a633271d..261f11fd 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,7 +10,7 @@ Consumers on net48 or net8+ built with VS 2022 17.14+ or .NET 10+ SDK are fully supported. --> 4.14.0 1.65.51 - 1.0.0-prerelease.27 + 1.0.0-prerelease.28 [3.2.8,) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 1f089a79..85c315a9 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -8,6 +8,7 @@ --> true false + true diff --git a/src/src/SourceGenerator/Emitters/MarkerAttributeTemplateEmitter.cs b/src/src/SourceGenerator/Emitters/MarkerAttributeTemplateEmitter.cs new file mode 100644 index 00000000..2b5c3dcf --- /dev/null +++ b/src/src/SourceGenerator/Emitters/MarkerAttributeTemplateEmitter.cs @@ -0,0 +1,1286 @@ +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Purview.Telemetry.SourceGenerator.Helpers; +using Purview.Telemetry.SourceGenerator.Templates; + +namespace Purview.Telemetry.SourceGenerator.Emitters; + +/// +/// Emits the marker-attribute templates injected into consuming compilations. Each template +/// previously shipped as a static embedded resource; it is now built in memory with a +/// inside RegisterPostInitializationOutput. +/// +static class MarkerAttributeTemplateEmitter +{ + static readonly Dictionary> Emitters = new(StringComparer.Ordinal) + { + ["TagAttribute"] = WriteTagLikeAttribute, + ["BaggageAttribute"] = WriteTagLikeAttribute, + ["ExcludeAttribute"] = (writer, type) => + WriteSimpleAttribute(writer, type, AttributeTargets.Method, includeSuppressMessage: false), + ["TelemetryGenerationAttribute"] = WriteTelemetryGenerationAttribute, + ["Targets"] = WriteTargetsEnum, + ["NamingConvention"] = WriteNamingConventionEnum, + ["ExcludeTargetsAttribute"] = WriteExcludeTargetsAttribute, + ["ActivitySourceGenerationAttribute"] = WriteActivitySourceGenerationAttribute, + ["ActivitySourceAttribute"] = WriteActivitySourceAttribute, + ["ActivityAttribute"] = WriteActivityAttribute, + ["EventAttribute"] = WriteEventAttribute, + ["ContextAttribute"] = (writer, type) => + WriteSimpleAttribute(writer, type, AttributeTargets.Method, includeSuppressMessage: false), + ["EscapeAttribute"] = (writer, type) => + WriteSimpleAttribute(writer, type, AttributeTargets.Parameter, includeSuppressMessage: false), + ["StatusDescriptionAttribute"] = (writer, type) => + WriteSimpleAttribute(writer, type, AttributeTargets.Parameter, includeSuppressMessage: false), + ["LoggerGenerationAttribute"] = WriteLoggerGenerationAttribute, + ["LoggerAttribute"] = WriteLoggerAttribute, + ["LogAttribute"] = WriteLogAttribute, + ["LogPrefixType"] = WriteLogPrefixTypeEnum, + ["LoggerGenerationMode"] = WriteLoggerGenerationModeEnum, + ["ExpandEnumerableAttribute"] = WriteExpandEnumerableAttribute, + ["TraceAttribute"] = WriteSpecificLogAttribute, + ["DebugAttribute"] = WriteSpecificLogAttribute, + ["InfoAttribute"] = WriteSpecificLogAttribute, + ["WarningAttribute"] = WriteSpecificLogAttribute, + ["ErrorAttribute"] = WriteSpecificLogAttribute, + ["CriticalAttribute"] = WriteSpecificLogAttribute, + ["MeterGenerationAttribute"] = WriteMeterGenerationAttribute, + ["MeterAttribute"] = WriteMeterAttribute, + ["MeterNameGenerationType"] = WriteMeterNameGenerationTypeEnum, + ["InstrumentMeasurementAttribute"] = (writer, type) => + WriteSimpleAttribute(writer, type, AttributeTargets.Parameter, includeSuppressMessage: false), + ["AutoCounterAttribute"] = WriteAutoCounterAttribute, + ["CounterAttribute"] = WriteCounterLikeAttribute, + ["UpDownCounterAttribute"] = WriteCounterLikeAttribute, + ["HistogramAttribute"] = WriteCounterLikeAttribute, + ["ObservableCounterAttribute"] = WriteObservableCounterLikeAttribute, + ["ObservableUpDownCounterAttribute"] = WriteObservableCounterLikeAttribute, + ["ObservableGaugeAttribute"] = WriteObservableCounterLikeAttribute, + }; + + public static void EmitAll(IncrementalGeneratorPostInitializationContext context) + { + var settings = GenerationSettings.Create(); + + foreach (var template in TemplateLibrary.GetAllTemplates()) + { + var writer = new CodeWriter(settings); + Emit(writer, template); + context.AddSource(template.GetGeneratedFilename(), SourceText.From(writer.ToString(), Encoding.UTF8)); + } + } + + static void Emit(CodeWriter writer, TemplateInfo template) + { + if (!Emitters.TryGetValue(template.Name, out var emit)) + throw new ArgumentOutOfRangeException( + nameof(template), + template.Name, + "Unknown marker-attribute template." + ); + + emit(writer, template.TypeInfo); + } + + // ------------------------------------------------------------------------------------------- + // File scaffolding + // ------------------------------------------------------------------------------------------- + + static void WriteHeader(CodeWriter writer) + { + writer + .WriteLine("// ") + .WriteLine("// This code was generated by the Purview.Telemetry.SourceGenerator.") + .WriteLine("// Changes to this file will be lost when the source generator runs again.") + .NewLine() + .WriteLine("#if !NET48_OR_GREATER && !PURVIEW_TELEMETRY_NON_NULLABLE") + .WriteLine("#nullable enable") + .WriteLine("#endif") + .NewLine() + .WriteLine("#pragma warning disable 1591") + .NewLine(); + } + + static void WriteFileScopedNamespace(CodeWriter writer) => writer.WriteFileScopedNamespace("Purview.Telemetry"); + + // ------------------------------------------------------------------------------------------- + // Attribute templates + // ------------------------------------------------------------------------------------------- + + /// Writes a complete attribute-template file. + static void EmitAttribute( + CodeWriter writer, + TypeIdentity type, + AttributeTargets targets, + Action body, + bool wrapInExcludeLoggingGuard = false, + bool includeSuppressMessage = true + ) + { + WriteHeader(writer); + + if (wrapInExcludeLoggingGuard) + writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); + + WriteFileScopedNamespace(writer); + WriteAttributeTemplate(writer, type, targets, body, includeSuppressMessage); + + if (wrapInExcludeLoggingGuard) + writer.WriteLine("#endif"); + } + + static void WriteAttributeTemplate( + CodeWriter writer, + TypeIdentity type, + AttributeTargets targets, + Action body, + bool includeSuppressMessage + ) + { + var attributes = ImmutableArray.Empty; + attributes = attributes.Add(ConditionalAttribute()); + if (includeSuppressMessage) + attributes = attributes.Add(SuppressMessageAttribute()); + + writer.WriteAttributeClass( + new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Internal) { Attributes = attributes }, + targets, + body + ); + } + + static void WriteSimpleAttribute( + CodeWriter writer, + TypeIdentity type, + AttributeTargets targets, + bool includeSuppressMessage + ) + { + EmitAttribute(writer, type, targets, static _ => { }, includeSuppressMessage: includeSuppressMessage); + } + + static AttributeDeclarationOptions ConditionalAttribute() => + new(new TypeIdentity("ConditionalAttribute", "System.Diagnostics")) + { + Arguments = [new AttributeArgumentOptions("\"PURVIEW_TELEMETRY_ATTRIBUTES\"")], + }; + + static AttributeDeclarationOptions SuppressMessageAttribute() => + new(new TypeIdentity("SuppressMessageAttribute", "System.Diagnostics.CodeAnalysis")) + { + Arguments = + [ + new AttributeArgumentOptions("\"Design\""), + new AttributeArgumentOptions("\"CA1019:Define accessors for attribute arguments\""), + ], + }; + + // ------------------------------------------------------------------------------------------- + // Members + // ------------------------------------------------------------------------------------------- + + static void WriteEmptyConstructor(CodeWriter writer, TypeIdentity type) => + writer.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), + static _ => { } + ); + + static void WriteNameConstructor(CodeWriter writer, TypeIdentity type) => + writer.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = [new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference())], + }, + ctor => ctor.WriteAssignment("Name", "name") + ); + + static void WriteMessageTemplateConstructor(CodeWriter writer, TypeIdentity type) => + writer.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("messageTemplate", TypeLibrary.System.String.AsTypeReference()), + ], + }, + ctor => ctor.WriteAssignment("MessageTemplate", "messageTemplate") + ); + + static void WriteEventIdConstructor(CodeWriter writer, TypeIdentity type) => + writer.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = [new ParameterDeclarationOptions("eventId", TypeLibrary.System.Int32.AsTypeReference())], + }, + ctor => ctor.WriteAssignment("EventId", "eventId") + ); + + /// Writes a public property with generated attributes and an optional initializer. + static void WritePublicProperty(CodeWriter writer, string name, TypeReference type, string? initializer = null) + { + writer.WriteProperty( + new PropertyDeclarationOptions(name, type, TypeDeclarationAccessibility.Public) + { + HasSetter = true, + Initializer = initializer, + } + ); + } + + /// + /// Writes a public nullable-capable string property inside the NET48_OR_GREATER/ + /// PURVIEW_TELEMETRY_NON_NULLABLE preprocessor guard used by the marker attributes. + /// + static void WriteNullableStringProperty(CodeWriter writer, string name) + { + writer.WriteLine("#if NET48_OR_GREATER || PURVIEW_TELEMETRY_NON_NULLABLE"); + writer.WriteProperty( + new PropertyDeclarationOptions( + name, + TypeLibrary.System.String.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) + { + HasSetter = true, + IncludeGeneratedAttributes = false, + } + ); + writer.WriteLine("#else"); + writer.WriteProperty( + new PropertyDeclarationOptions( + name, + TypeLibrary.System.String.AsTypeReference().Nullable(), + TypeDeclarationAccessibility.Public + ) + { + HasSetter = true, + IncludeGeneratedAttributes = false, + } + ); + writer.WriteLine("#endif"); + } + + /// Writes a public non-nullable string property (used for defaults that always have a value). + static void WritePlainStringProperty(CodeWriter writer, string name, string? initializer = null) + { + writer.WriteProperty( + new PropertyDeclarationOptions( + name, + TypeLibrary.System.String.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) + { + HasSetter = true, + IncludeGeneratedAttributes = false, + Initializer = initializer, + } + ); + } + + // ------------------------------------------------------------------------------------------- + // Shared templates + // ------------------------------------------------------------------------------------------- + + static void WriteTagLikeAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Parameter, + body => + { + WriteEmptyConstructor(body, type); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions( + "skipOnNullOrEmpty", + TypeLibrary.System.Boolean.AsTypeReference() + ), + ], + }, + ctor => ctor.WriteAssignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty") + ); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions( + "skipOnNullOrEmpty", + TypeLibrary.System.Boolean.AsTypeReference() + ) + { + DefaultValue = "false", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("Name", "name"); + ctor.WriteAssignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty"); + } + ); + + WriteNullableStringProperty(body, "Name"); + WritePublicProperty(body, "SkipOnNullOrEmpty", TypeLibrary.System.Boolean.AsTypeReference()); + } + ); + } + + static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity type) + { + var namingConvention = TypeLibrary.Telemetry.NamingConventionEnum; + + EmitAttribute( + writer, + type, + AttributeTargets.Assembly | AttributeTargets.Interface, + body => + { + WriteEmptyConstructor(body, type); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions( + "generateDependencyExtension", + TypeLibrary.System.Boolean.AsTypeReference() + ), + new ParameterDeclarationOptions("className", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + }, + new ParameterDeclarationOptions( + "dependencyInjectionClassName", + TypeLibrary.System.String.AsTypeReference() + ) + { + DefaultValue = "null", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("GenerateDependencyExtension", "generateDependencyExtension"); + ctor.WriteAssignment("ClassName", "className"); + ctor.WriteAssignment("DependencyInjectionClassName", "dependencyInjectionClassName"); + } + ); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("className", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions( + "dependencyInjectionClassName", + TypeLibrary.System.String.AsTypeReference() + ) + { + DefaultValue = "null", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("ClassName", "className"); + ctor.WriteAssignment("DependencyInjectionClassName", "dependencyInjectionClassName"); + } + ); + + WritePublicProperty( + body, + "GenerateDependencyExtension", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WriteNullableStringProperty(body, "ClassName"); + WriteNullableStringProperty(body, "DependencyInjectionClassName"); + WritePublicProperty( + body, + "DependencyInjectionClassIsPublic", + TypeLibrary.System.Boolean.AsTypeReference() + ); + WritePublicProperty( + body, + "NamingConvention", + namingConvention.AsTypeReference(), + $"{namingConvention.RenderFullName}.OpenTelemetry" + ); + WritePublicProperty( + body, + "GenerateTelemetryNamesClass", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WriteNullableStringProperty(body, "TelemetryNamesClassName"); + } + ); + } + + static void WriteExcludeTargetsAttribute(CodeWriter writer, TypeIdentity type) + { + var targets = TypeLibrary.Telemetry.TargetsEnum; + + EmitAttribute( + writer, + type, + AttributeTargets.Parameter, + body => + { + body.XmlSummary("Constructs a new instance with the specified targets to exclude."); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = [new ParameterDeclarationOptions("targets", targets.AsTypeReference())], + }, + ctor => ctor.WriteAssignment("ExcludedTargets", "targets") + ); + + body.XmlSummary("Gets or sets the targets to exclude for this parameter."); + WritePublicProperty(body, "ExcludedTargets", targets.AsTypeReference()); + } + ); + } + + // ------------------------------------------------------------------------------------------- + // Activity templates + // ------------------------------------------------------------------------------------------- + + static void WriteActivitySourceGenerationAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Assembly, + body => + { + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions( + "defaultToTags", + TypeLibrary.System.Boolean.AsTypeReference() + ) + { + DefaultValue = "true", + }, + new ParameterDeclarationOptions( + "generateDiagnosticsForMissingActivity", + TypeLibrary.System.Boolean.AsTypeReference() + ) + { + DefaultValue = "true", + }, + ], + }, + ctor => + { + ctor.WriteLine( + "if (string.IsNullOrWhiteSpace(name)) throw new System.ArgumentNullException(nameof(name));" + ); + ctor.WriteAssignment("Name", "name"); + ctor.WriteAssignment("DefaultToTags", "defaultToTags"); + ctor.WriteAssignment( + "GenerateDiagnosticsForMissingActivity", + "generateDiagnosticsForMissingActivity" + ); + } + ); + + WriteNullableStringProperty(body, "Name"); + WritePublicProperty(body, "DefaultToTags", TypeLibrary.System.Boolean.AsTypeReference(), "true"); + WriteNullableStringProperty(body, "BaggageAndTagPrefix"); + WritePlainStringProperty(body, "BaggageAndTagSeparator", "\".\""); + WritePublicProperty( + body, + "LowercaseBaggageAndTagKeys", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WritePublicProperty( + body, + "GenerateDiagnosticsForMissingActivity", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + } + ); + } + + static void WriteActivitySourceAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Interface, + body => + { + body.XmlSummary($"Constructs a new instance of the {XmlSee("ActivitySourceAttribute")}."); + WriteEmptyConstructor(body, type); + + body.XmlSummary($"Constructs a new instance specifying the {XmlSee("Name")}."); + body.XmlParam("name", $"The {XmlSee("Name")}."); + WriteNameConstructor(body, type); + + WriteNullableStringProperty(body, "Name"); + body.XmlSummary("Specifies the default when inferring between tag or baggage."); + WritePublicProperty(body, "DefaultToTags", TypeLibrary.System.Boolean.AsTypeReference(), "true"); + WriteNullableStringProperty(body, "BaggageAndTagPrefix"); + body.XmlSummary("Determines if the name is used as a prefix."); + WritePublicProperty( + body, + "IncludeActivitySourcePrefix", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + body.XmlSummary("Determines if tag/ baggage names are lowercased."); + WritePublicProperty( + body, + "LowercaseBaggageAndTagKeys", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + } + ); + } + + static void WriteActivityAttribute(CodeWriter writer, TypeIdentity type) + { + var activityKind = TypeLibrary.Activities.SystemDiagnostics.ActivityKind; + + EmitAttribute( + writer, + type, + AttributeTargets.Method, + body => + { + WriteEmptyConstructor(body, type); + WriteNameConstructor(body, type); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = [new ParameterDeclarationOptions("kind", activityKind.AsTypeReference())], + }, + ctor => ctor.WriteAssignment("Kind", "kind") + ); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions("kind", activityKind.AsTypeReference()) + { + DefaultValue = $"{activityKind.RenderFullName}.Internal", + }, + new ParameterDeclarationOptions("createOnly", TypeLibrary.System.Boolean.AsTypeReference()) + { + DefaultValue = "false", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("Name", "name"); + ctor.WriteAssignment("Kind", "kind"); + ctor.WriteAssignment("CreateOnly", "createOnly"); + } + ); + + WriteNullableStringProperty(body, "Name"); + WritePublicProperty(body, "Kind", activityKind.AsTypeReference()); + WritePublicProperty(body, "CreateOnly", TypeLibrary.System.Boolean.AsTypeReference()); + } + ); + } + + static void WriteEventAttribute(CodeWriter writer, TypeIdentity type) + { + var statusCode = TypeLibrary.Activities.SystemDiagnostics.ActivityStatusCode; + + EmitAttribute( + writer, + type, + AttributeTargets.Method, + body => + { + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("statusCode", statusCode.AsTypeReference()) + { + DefaultValue = $"{statusCode.RenderFullName}.Unset", + }, + ], + }, + ctor => ctor.WriteAssignment("StatusCode", "statusCode") + ); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions( + "useRecordExceptionRules", + TypeLibrary.System.Boolean.AsTypeReference() + ) + { + DefaultValue = "true", + }, + new ParameterDeclarationOptions( + "recordExceptionAsEscaped", + TypeLibrary.System.Boolean.AsTypeReference() + ) + { + DefaultValue = "true", + }, + new ParameterDeclarationOptions("statusCode", statusCode.AsTypeReference()) + { + DefaultValue = $"{statusCode.RenderFullName}.Unset", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("Name", "name"); + ctor.WriteAssignment("UseRecordExceptionRules", "useRecordExceptionRules"); + ctor.WriteAssignment("RecordExceptionAsEscaped", "recordExceptionAsEscaped"); + ctor.WriteAssignment("StatusCode", "statusCode"); + } + ); + + WriteNullableStringProperty(body, "Name"); + WritePublicProperty( + body, + "UseRecordExceptionRules", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WritePublicProperty( + body, + "RecordExceptionAsEscaped", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WritePublicProperty(body, "StatusCode", statusCode.AsTypeReference()); + WriteNullableStringProperty(body, "StatusDescription"); + } + ); + } + + // ------------------------------------------------------------------------------------------- + // Logging templates + // ------------------------------------------------------------------------------------------- + + static void WriteLoggerGenerationAttribute(CodeWriter writer, TypeIdentity type) + { + var logLevel = TypeLibrary.Logging.MicrosoftExtensions.LogLevel; + + EmitAttribute( + writer, + type, + AttributeTargets.Assembly, + body => + { + WriteEmptyConstructor(body, type); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = [new ParameterDeclarationOptions("defaultLevel", logLevel.AsTypeReference())], + }, + ctor => ctor.WriteAssignment("DefaultLevel", "defaultLevel") + ); + + WritePublicProperty( + body, + "DefaultLevel", + logLevel.AsTypeReference(), + $"{logLevel.RenderFullName}.Information" + ); + WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); + WritePublicProperty(body, "DefaultPrefixType", TypeLibrary.Logging.LogPrefixType.AsTypeReference()); + }, + wrapInExcludeLoggingGuard: true + ); + } + + static void WriteLoggerAttribute(CodeWriter writer, TypeIdentity type) + { + var logLevel = TypeLibrary.Logging.MicrosoftExtensions.LogLevel; + var logPrefixType = TypeLibrary.Logging.LogPrefixType; + + EmitAttribute( + writer, + type, + AttributeTargets.Interface, + body => + { + WriteEmptyConstructor(body, type); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("defaultLevel", logLevel.AsTypeReference()), + new ParameterDeclarationOptions("customPrefix", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("DefaultLevel", "defaultLevel"); + ctor.WriteAssignment("CustomPrefix", "customPrefix"); + ctor.WriteIfBlock( + "!string.IsNullOrWhiteSpace(CustomPrefix)", + block => block.WriteAssignment("PrefixType", $"{logPrefixType.RenderFullName}.Custom") + ); + } + ); + + WritePublicProperty( + body, + "DefaultLevel", + logLevel.AsTypeReference(), + $"{logLevel.RenderFullName}.Information" + ); + WriteNullableStringProperty(body, "CustomPrefix"); + WritePublicProperty(body, "PrefixType", logPrefixType.AsTypeReference()); + WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); + }, + wrapInExcludeLoggingGuard: true + ); + } + + static void WriteLogAttribute(CodeWriter writer, TypeIdentity type) + { + var logLevel = TypeLibrary.Logging.MicrosoftExtensions.LogLevel; + + EmitAttribute( + writer, + type, + AttributeTargets.Method, + body => + { + WriteEmptyConstructor(body, type); + WriteMessageTemplateConstructor(body, type); + WriteEventIdConstructor(body, type); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("level", logLevel.AsTypeReference()), + new ParameterDeclarationOptions( + "messageTemplate", + TypeLibrary.System.String.AsTypeReference() + ) + { + DefaultValue = "null", + }, + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("Level", "level"); + ctor.WriteAssignment("MessageTemplate", "messageTemplate"); + ctor.WriteAssignment("Name", "name"); + } + ); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("eventId", TypeLibrary.System.Int32.AsTypeReference()), + new ParameterDeclarationOptions("level", logLevel.AsTypeReference()), + new ParameterDeclarationOptions( + "messageTemplate", + TypeLibrary.System.String.AsTypeReference() + ) + { + DefaultValue = "null", + }, + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("Level", "level"); + ctor.WriteAssignment("MessageTemplate", "messageTemplate"); + ctor.WriteAssignment("EventId", "eventId"); + ctor.WriteAssignment("Name", "name"); + } + ); + + WritePublicProperty( + body, + "Level", + logLevel.AsTypeReference(), + $"{logLevel.RenderFullName}.Information" + ); + WriteNullableStringProperty(body, "MessageTemplate"); + WritePublicProperty(body, "EventId", TypeLibrary.System.Int32.AsTypeReference().Nullable()); + WritePublicProperty(body, "Name", TypeLibrary.System.String.AsTypeReference().Nullable()); + WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); + }, + wrapInExcludeLoggingGuard: true + ); + } + + static void WriteSpecificLogAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Method, + body => + { + WriteMessageTemplateConstructor(body, type); + WriteEventIdConstructor(body, type); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions( + "messageTemplate", + TypeLibrary.System.String.AsTypeReference() + ) + { + DefaultValue = "null", + }, + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("MessageTemplate", "messageTemplate"); + ctor.WriteAssignment("Name", "name"); + } + ); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("eventId", TypeLibrary.System.Int32.AsTypeReference()), + new ParameterDeclarationOptions( + "messageTemplate", + TypeLibrary.System.String.AsTypeReference() + ) + { + DefaultValue = "null", + }, + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("MessageTemplate", "messageTemplate"); + ctor.WriteAssignment("EventId", "eventId"); + ctor.WriteAssignment("Name", "name"); + } + ); + + WriteNullableStringProperty(body, "MessageTemplate"); + WritePublicProperty(body, "EventId", TypeLibrary.System.Int32.AsTypeReference().Nullable()); + WriteNullableStringProperty(body, "Name"); + WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); + }, + wrapInExcludeLoggingGuard: true + ); + } + + static void WriteExpandEnumerableAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Parameter, + body => + { + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions( + "maximumValueCount", + TypeLibrary.System.Int32.AsTypeReference() + ) + { + DefaultValue = "5", + }, + ], + }, + ctor => ctor.WriteAssignment("MaximumValueCount", "maximumValueCount") + ); + + body.XmlSummary("Gets or sets the maximum number of values to include when expanding an enumerable."); + WritePublicProperty(body, "MaximumValueCount", TypeLibrary.System.Int32.AsTypeReference()); + }, + wrapInExcludeLoggingGuard: true + ); + } + + // ------------------------------------------------------------------------------------------- + // Metrics templates + // ------------------------------------------------------------------------------------------- + + static void WriteMeterGenerationAttribute(CodeWriter writer, TypeIdentity type) + { + var nameGenerationType = TypeLibrary.Metrics.MeterNameGenerationType; + + EmitAttribute( + writer, + type, + AttributeTargets.Assembly, + body => + { + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("meterName", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + }, + new ParameterDeclarationOptions("nameGenerationType", nameGenerationType.AsTypeReference()) + { + DefaultValue = $"{nameGenerationType.RenderFullName}.DotNet", + }, + new ParameterDeclarationOptions( + "instrumentPrefix", + TypeLibrary.System.String.AsTypeReference() + ) + { + DefaultValue = "null", + }, + new ParameterDeclarationOptions( + "lowercaseInstrumentName", + TypeLibrary.System.Boolean.AsTypeReference() + ) + { + DefaultValue = "true", + }, + new ParameterDeclarationOptions( + "lowercaseTagKeys", + TypeLibrary.System.Boolean.AsTypeReference() + ) + { + DefaultValue = "true", + }, + ], + }, + ctor => + { + ctor.WriteAssignment("MeterName", "meterName"); + ctor.WriteAssignment("MeterNameGenerationType", "nameGenerationType"); + ctor.WriteAssignment("InstrumentPrefix", "instrumentPrefix"); + ctor.WriteAssignment("LowercaseInstrumentName", "lowercaseInstrumentName"); + ctor.WriteAssignment("LowercaseTagKeys", "lowercaseTagKeys"); + } + ); + + WriteNullableStringProperty(body, "MeterName"); + WritePublicProperty( + body, + "MeterNameGenerationType", + nameGenerationType.AsTypeReference(), + $"{nameGenerationType.RenderFullName}.DotNet" + ); + WriteNullableStringProperty(body, "InstrumentPrefix"); + WritePlainStringProperty(body, "InstrumentSeparator", "\".\""); + WritePublicProperty( + body, + "LowercaseInstrumentName", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WritePublicProperty(body, "LowercaseTagKeys", TypeLibrary.System.Boolean.AsTypeReference(), "true"); + } + ); + } + + static void WriteMeterAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Interface, + body => + { + WriteEmptyConstructor(body, type); + WriteNameConstructor(body, type); + + WriteNullableStringProperty(body, "Name"); + WriteNullableStringProperty(body, "InstrumentPrefix"); + WritePublicProperty( + body, + "IncludeAssemblyInstrumentPrefix", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WritePublicProperty( + body, + "LowercaseInstrumentName", + TypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WritePublicProperty(body, "LowercaseTagKeys", TypeLibrary.System.Boolean.AsTypeReference(), "true"); + } + ); + } + + static void WriteAutoCounterAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Method, + body => + { + WriteEmptyConstructor(body, type); + WriteNameUnitDescriptionConstructor(body, type); + + WriteNullableStringProperty(body, "Name"); + WriteNullableStringProperty(body, "Unit"); + WriteNullableStringProperty(body, "Description"); + } + ); + } + + static void WriteCounterLikeAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Method, + body => + { + WriteEmptyConstructor(body, type); + body.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions( + "autoIncrement", + TypeLibrary.System.Boolean.AsTypeReference() + ), + ], + }, + ctor => ctor.WriteAssignment("AutoIncrement", "autoIncrement") + ); + WriteNameUnitDescriptionConstructor(body, type, appendAutoIncrement: true); + + WritePublicProperty(body, "AutoIncrement", TypeLibrary.System.Boolean.AsTypeReference()); + WriteNullableStringProperty(body, "Name"); + WriteNullableStringProperty(body, "Unit"); + WriteNullableStringProperty(body, "Description"); + } + ); + } + + static void WriteObservableCounterLikeAttribute(CodeWriter writer, TypeIdentity type) + { + EmitAttribute( + writer, + type, + AttributeTargets.Method, + body => + { + WriteEmptyConstructor(body, type); + WriteNameUnitDescriptionConstructor(body, type, appendThrowOnAlreadyInitialized: true); + + WritePublicProperty(body, "AutoIncrement", TypeLibrary.System.Boolean.AsTypeReference()); + WriteNullableStringProperty(body, "Name"); + WriteNullableStringProperty(body, "Unit"); + WriteNullableStringProperty(body, "Description"); + WritePublicProperty(body, "ThrowOnAlreadyInitialized", TypeLibrary.System.Boolean.AsTypeReference()); + } + ); + } + + static void WriteNameUnitDescriptionConstructor( + CodeWriter writer, + TypeIdentity type, + bool appendAutoIncrement = false, + bool appendThrowOnAlreadyInitialized = false + ) + { + writer.WriteConstructor( + new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Parameters = BuildNameUnitDescriptionParameters(appendAutoIncrement, appendThrowOnAlreadyInitialized), + }, + ctor => + { + ctor.WriteAssignment("Name", "name"); + ctor.WriteAssignment("Unit", "unit"); + ctor.WriteAssignment("Description", "description"); + if (appendAutoIncrement) + ctor.WriteAssignment("AutoIncrement", "autoIncrement"); + if (appendThrowOnAlreadyInitialized) + ctor.WriteAssignment("ThrowOnAlreadyInitialized", "throwOnAlreadyInitialized"); + } + ); + } + + static ImmutableArray BuildNameUnitDescriptionParameters( + bool appendAutoIncrement, + bool appendThrowOnAlreadyInitialized + ) + { + var parameters = ImmutableArray.Empty; + parameters = parameters.Add( + new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + ); + parameters = parameters.Add( + new ParameterDeclarationOptions("unit", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + } + ); + parameters = parameters.Add( + new ParameterDeclarationOptions("description", TypeLibrary.System.String.AsTypeReference()) + { + DefaultValue = "null", + } + ); + if (appendAutoIncrement) + parameters = parameters.Add( + new ParameterDeclarationOptions("autoIncrement", TypeLibrary.System.Boolean.AsTypeReference()) + { + DefaultValue = "false", + } + ); + if (appendThrowOnAlreadyInitialized) + parameters = parameters.Add( + new ParameterDeclarationOptions( + "throwOnAlreadyInitialized", + TypeLibrary.System.Boolean.AsTypeReference() + ) + { + DefaultValue = "false", + } + ); + + return parameters; + } + + // ------------------------------------------------------------------------------------------- + // Enum templates + // ------------------------------------------------------------------------------------------- + + static void WriteTargetsEnum(CodeWriter writer, TypeIdentity type) + { + WriteHeader(writer); + WriteFileScopedNamespace(writer); + + writer.XmlSummary("Determines which telemetry targets a parameter is excluded from."); + writer.WriteEnum( + new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + { + Attributes = [new AttributeDeclarationOptions(new TypeIdentity("FlagsAttribute", "System"))], + }, + new EnumFieldDeclarationOptions("None", 0), + new EnumFieldDeclarationOptions("Activities", 1), + new EnumFieldDeclarationOptions("Logging", 2), + new EnumFieldDeclarationOptions("Metrics", 4), + new EnumFieldDeclarationOptions("All", "Activities | Logging | Metrics") + ); + } + + static void WriteNamingConventionEnum(CodeWriter writer, TypeIdentity type) + { + WriteHeader(writer); + WriteFileScopedNamespace(writer); + + writer.XmlSummary("Determines the naming convention used for generated telemetry names."); + writer.WriteEnum( + new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), + new EnumFieldDeclarationOptions("Legacy", 0), + new EnumFieldDeclarationOptions("OpenTelemetry", 1) + ); + } + + static void WriteLogPrefixTypeEnum(CodeWriter writer, TypeIdentity type) + { + WriteHeader(writer); + writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); + WriteFileScopedNamespace(writer); + + writer.XmlSummary("Determines the mode used to generate or override the prefix for the log entry."); + writer.WriteEnum( + new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), + new EnumFieldDeclarationOptions("Default", 0), + new EnumFieldDeclarationOptions("Interface", 1), + new EnumFieldDeclarationOptions("Class", 2), + new EnumFieldDeclarationOptions("Custom", 3), + new EnumFieldDeclarationOptions("TrimmedClassName", 4) + ); + + writer.WriteLine("#endif"); + } + + static void WriteLoggerGenerationModeEnum(CodeWriter writer, TypeIdentity type) + { + WriteHeader(writer); + writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); + WriteFileScopedNamespace(writer); + + writer.XmlSummary("Controls the generation mode used for log methods."); + writer.WriteEnum( + new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), + new EnumFieldDeclarationOptions("Auto", 0), + new EnumFieldDeclarationOptions("V1", 1), + new EnumFieldDeclarationOptions("V2", 2) + ); + + writer.WriteLine("#endif"); + } + + static void WriteMeterNameGenerationTypeEnum(CodeWriter writer, TypeIdentity type) + { + WriteHeader(writer); + WriteFileScopedNamespace(writer); + + writer.XmlSummary("Determines how meter names are generated when not explicitly specified."); + writer.WriteEnum( + new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), + new EnumFieldDeclarationOptions("OpenTelemetry", 0), + new EnumFieldDeclarationOptions("DotNet", 1) + ); + } +} diff --git a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.cs b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.cs index b71f1dfa..a59b2dd8 100644 --- a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.cs +++ b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Purview.Telemetry.SourceGenerator.Emitters; using Purview.Telemetry.SourceGenerator.Helpers; using Purview.Telemetry.SourceGenerator.Records; @@ -46,8 +47,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // assemblies, preventing CS0436 conflicts when multiple projects reference this generator. ctx.AddEmbeddedAttributeDefinition(); - foreach (var template in TemplateLibrary.GetAllTemplates()) - ctx.AddSource(template.GetGeneratedFilename(), template.TemplateData); + // Marker-attribute templates are emitted through a CodeWriter (see + // MarkerAttributeTemplateEmitter) rather than loaded from embedded resources. + MarkerAttributeTemplateEmitter.EmitAll(ctx); }); // The generation context carries the framework logging sink, settings and the diff --git a/src/src/SourceGenerator/SourceGenerator.csproj b/src/src/SourceGenerator/SourceGenerator.csproj index 51e638ad..594963d4 100644 --- a/src/src/SourceGenerator/SourceGenerator.csproj +++ b/src/src/SourceGenerator/SourceGenerator.csproj @@ -34,20 +34,11 @@ /> - - - - - - - - - - - + + + -/// Emits the injected marker-attribute templates via instead of -/// loading them from embedded resources. Output is deterministic (no timestamp). -/// -static class SourceEmitter -{ - public static string Emit(string typeName) => - typeName switch - { - "Targets" => EmitTargets(), - "NamingConvention" => EmitNamingConvention(), - "LogPrefixType" => EmitLogPrefixType(), - "LoggerGenerationMode" => EmitLoggerGenerationMode(), - "MeterNameGenerationType" => EmitMeterNameGenerationType(), - "ActivitySourceAttribute" => EmitActivitySourceAttribute(), - "ActivitySourceGenerationAttribute" => EmitActivitySourceGenerationAttribute(), - "ActivityAttribute" => EmitActivityAttribute(), - "EventAttribute" => EmitEventAttribute(), - "ContextAttribute" => EmitMarkerAttribute("ContextAttribute", AttributeTargets.Method), - "BaggageAttribute" => EmitBaggageAttribute(), - "EscapeAttribute" => EmitMarkerAttribute("EscapeAttribute", AttributeTargets.Parameter), - "StatusDescriptionAttribute" => EmitMarkerAttribute( - "StatusDescriptionAttribute", - AttributeTargets.Parameter - ), - "TelemetryGenerationAttribute" => EmitTelemetryGenerationAttribute(), - "TagAttribute" => EmitTagAttribute(), - "ExcludeAttribute" => EmitMarkerAttribute("ExcludeAttribute", AttributeTargets.Method), - "ExcludeTargetsAttribute" => EmitExcludeTargetsAttribute(), - "LoggerGenerationAttribute" => EmitLoggerGenerationAttribute(), - "LoggerAttribute" => EmitLoggerAttribute(), - "LogAttribute" => EmitLogAttribute(), - "ExpandEnumerableAttribute" => EmitExpandEnumerableAttribute(), - "TraceAttribute" => EmitSpecificLogAttribute("TraceAttribute"), - "DebugAttribute" => EmitSpecificLogAttribute("DebugAttribute"), - "InfoAttribute" => EmitSpecificLogAttribute("InfoAttribute"), - "WarningAttribute" => EmitSpecificLogAttribute("WarningAttribute"), - "ErrorAttribute" => EmitSpecificLogAttribute("ErrorAttribute"), - "CriticalAttribute" => EmitSpecificLogAttribute("CriticalAttribute"), - "MeterGenerationAttribute" => EmitMeterGenerationAttribute(), - "MeterAttribute" => EmitMeterAttribute(), - "InstrumentMeasurementAttribute" => EmitMarkerAttribute( - "InstrumentMeasurementAttribute", - AttributeTargets.Parameter - ), - "AutoCounterAttribute" => EmitInstrumentAttribute( - "AutoCounterAttribute", - autoIncrement: true, - observable: false - ), - "CounterAttribute" => EmitInstrumentAttribute("CounterAttribute", autoIncrement: false, observable: false), - "UpDownCounterAttribute" => EmitInstrumentAttribute( - "UpDownCounterAttribute", - autoIncrement: false, - observable: false - ), - "HistogramAttribute" => EmitInstrumentAttribute( - "HistogramAttribute", - autoIncrement: false, - observable: false - ), - "ObservableCounterAttribute" => EmitInstrumentAttribute( - "ObservableCounterAttribute", - autoIncrement: false, - observable: true - ), - "ObservableUpDownCounterAttribute" => EmitInstrumentAttribute( - "ObservableUpDownCounterAttribute", - autoIncrement: false, - observable: true - ), - "ObservableGaugeAttribute" => EmitInstrumentAttribute( - "ObservableGaugeAttribute", - autoIncrement: false, - observable: true - ), - _ => throw new ArgumentOutOfRangeException(nameof(typeName), typeName, "Unknown template."), - }; - - // --------------------------------------------------------------------------------------- - // Shared helpers - // --------------------------------------------------------------------------------------- - - static CodeWriter CreateWriter(bool logging = false) - { - var writer = new CodeWriter(GenerationSettings.Create()); - writer - .WriteLine("// ") - .WriteLine("// This code was generated by the Purview.Telemetry.SourceGenerator.") - .WriteLine("// Changes to this file will be lost when the source generator runs again.") - .NewLine() - .WriteLine("#if !NET48_OR_GREATER && !PURVIEW_TELEMETRY_NON_NULLABLE") - .WriteLine("#nullable enable") - .WriteLine("#endif") - .NewLine() - .WriteLine("#pragma warning disable 1591") - .NewLine(); - - if (logging) - writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); - - return writer; - } - - static AttributeDeclarationOptions ConditionalAttribute() => - new(new TypeIdentity("ConditionalAttribute", "System.Diagnostics")) - { - Arguments = [new("\"PURVIEW_TELEMETRY_ATTRIBUTES\"")], - }; - - static AttributeDeclarationOptions SuppressMessageAttribute() => - new(new TypeIdentity("SuppressMessageAttribute", "System.Diagnostics.CodeAnalysis")) - { - Arguments = [new("\"Design\""), new("\"CA1019:Define accessors for attribute arguments\"")], - }; - - /// Writes a property whose nullability varies with the target framework. - static void WriteProperty( - CodeWriter writer, - string type, - string name, - bool nullable, - bool hasDefault = false, - string? defaultValue = null - ) - { - if (!nullable) - { - writer.Write("public ").Write(type).Write(' ').Write(name).Write(" { get; set; }"); - if (hasDefault) - writer.Write(" = ").Write(defaultValue).Write(';'); - writer.WriteLine(); - return; - } - - writer.WriteLine("#if NET48_OR_GREATER || PURVIEW_TELEMETRY_NON_NULLABLE"); - writer.Write("public ").Write(type).Write(' ').Write(name).Write(" { get; set; }"); - if (hasDefault) - writer.Write(" = ").Write(defaultValue).Write(';'); - writer.WriteLine().WriteLine("#else"); - writer.Write("public ").Write(type).Write("? ").Write(name).Write(" { get; set; }"); - if (hasDefault) - writer.Write(" = ").Write(defaultValue).Write(';'); - writer.WriteLine().WriteLine("#endif"); - } - - /// Writes a nullable string property, conditionally emitting the non-nullable form. - static void WriteStringProperty(CodeWriter writer, string name, bool nullable) => - WriteProperty(writer, "string", name, nullable); - - /// Writes a marker attribute (no members) with the standard decorations. - static string EmitMarkerAttribute(string typeName, AttributeTargets targets) - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new(typeName, TypeDeclarationAccessibility.Internal) { Attributes = [ConditionalAttribute()] }, - targets, - static _ => { } - ); - - return writer.ToString(); - } - - // --------------------------------------------------------------------------------------- - // Enums - // --------------------------------------------------------------------------------------- - - static string EmitTargets() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer - .XmlSummary("Determines which telemetry targets a parameter is excluded from.") - .WriteEnum( - new TypeDeclarationOptions("Targets", TypeDeclarationAccessibility.Public) - { - Attributes = [new AttributeDeclarationOptions(new TypeIdentity("FlagsAttribute", "System"))], - }, - new EnumFieldDeclarationOptions("None", 0), - new EnumFieldDeclarationOptions("Activities", 1), - new EnumFieldDeclarationOptions("Logging", 2), - new EnumFieldDeclarationOptions("Metrics", 4), - new EnumFieldDeclarationOptions("All", "Activities | Logging | Metrics") - ); - - return writer.ToString(); - } - - static string EmitNamingConvention() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer - .XmlSummary("Determines the naming convention used for generated telemetry names.") - .WriteEnum( - new TypeDeclarationOptions("NamingConvention", TypeDeclarationAccessibility.Public), - new EnumFieldDeclarationOptions("Legacy", 0), - new EnumFieldDeclarationOptions("OpenTelemetry", 1) - ); - - return writer.ToString(); - } - - static string EmitLogPrefixType() - { - var writer = CreateWriter(logging: true); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer - .XmlSummary("Determines the mode used to generate or override the prefix for the log entry.") - .WriteEnum( - new TypeDeclarationOptions("LogPrefixType", TypeDeclarationAccessibility.Public), - new EnumFieldDeclarationOptions("Default", 0), - new EnumFieldDeclarationOptions("Interface", 1), - new EnumFieldDeclarationOptions("Class", 2), - new EnumFieldDeclarationOptions("Custom", 3), - new EnumFieldDeclarationOptions("TrimmedClassName", 4) - ); - - writer.WriteLine("#endif"); - return writer.ToString(); - } - - static string EmitLoggerGenerationMode() - { - var writer = CreateWriter(logging: true); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer - .XmlSummary("Controls the generation mode used for log methods.") - .WriteEnum( - new TypeDeclarationOptions("LoggerGenerationMode", TypeDeclarationAccessibility.Public), - new EnumFieldDeclarationOptions("Auto", 0), - new EnumFieldDeclarationOptions("V1", 1), - new EnumFieldDeclarationOptions("V2", 2) - ); - - writer.WriteLine("#endif"); - return writer.ToString(); - } - - static string EmitMeterNameGenerationType() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer - .XmlSummary("Determines how meter names are generated when not explicitly specified.") - .WriteEnum( - new TypeDeclarationOptions("MeterNameGenerationType", TypeDeclarationAccessibility.Public), - new EnumFieldDeclarationOptions("OpenTelemetry", 0), - new EnumFieldDeclarationOptions("DotNet", 1) - ); - - return writer.ToString(); - } - - // --------------------------------------------------------------------------------------- - // Activities - // --------------------------------------------------------------------------------------- - - static string EmitActivitySourceAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("ActivitySourceAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Interface, - body => - { - body.XmlSummary("Constructs a new instance of the .") - .WriteConstructor( - new("ActivitySourceAttribute", TypeDeclarationAccessibility.Public), - static constructorWriter => constructorWriter.Comment("Empty") - ) - .XmlSummary("Constructs a new instance specifying the .") - .XmlParam("name", "The .") - .WriteConstructor( - new("ActivitySourceAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = [new("name", PurviewTypeLibrary.System.String)], - }, - static constructorWriter => constructorWriter.WriteLine("Name = name;") - ); - - WriteStringProperty(body, "Name", nullable: true); - body.XmlSummary("Specifies the default when inferring between tag or baggage.") - .WriteProperty( - new("DefaultToTags", PurviewTypeLibrary.System.Boolean, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - Initializer = "true", - } - ); - WriteStringProperty(body, "BaggageAndTagPrefix", nullable: true); - body.XmlSummary("Determines if the name is used as a prefix.") - .WriteProperty( - new( - "IncludeActivitySourcePrefix", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ) - .XmlSummary("Determines if tag/ baggage names are lowercased.") - .WriteProperty( - new( - "LowercaseBaggageAndTagKeys", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ); - } - ); - - return writer.ToString(); - } - - static string EmitActivitySourceGenerationAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("ActivitySourceGenerationAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Assembly, - body => - { - body.WriteConstructor( - new("ActivitySourceGenerationAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("name", PurviewTypeLibrary.System.String), - new("defaultToTags", PurviewTypeLibrary.System.Boolean) { DefaultValue = "true" }, - new("generateDiagnosticsForMissingActivity", PurviewTypeLibrary.System.Boolean) - { - DefaultValue = "true", - }, - ], - }, - static constructorWriter => - constructorWriter - .WriteLine( - "if (string.IsNullOrWhiteSpace(name)) throw new System.ArgumentNullException(nameof(name));" - ) - .WriteAssignment("Name", "name") - .WriteAssignment("DefaultToTags", "defaultToTags") - .WriteAssignment( - "GenerateDiagnosticsForMissingActivity", - "generateDiagnosticsForMissingActivity" - ) - ); - - WriteStringProperty(body, "Name", nullable: true); - body.WriteProperty( - new("DefaultToTags", PurviewTypeLibrary.System.Boolean, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - Initializer = "true", - } - ); - WriteStringProperty(body, "BaggageAndTagPrefix", nullable: true); - WriteProperty(body, "string", "BaggageAndTagSeparator", false, true, "\".\""); - body.WriteProperty( - new( - "LowercaseBaggageAndTagKeys", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ) - .WriteProperty( - new( - "GenerateDiagnosticsForMissingActivity", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ); - } - ); - - return writer.ToString(); - } - - static string EmitActivityAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("ActivityAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Method, - body => - { - body.WriteConstructor( - new("ActivityAttribute", TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ) - .WriteConstructor( - new("ActivityAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = [new("name", PurviewTypeLibrary.System.String)], - }, - static constructorWriter => constructorWriter.WriteAssignment("Name", "name") - ) - .WriteConstructor( - new("ActivityAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("kind", TypeLibrary.Activities.SystemDiagnostics.ActivityKind.AsTypeReference()), - ], - }, - static constructorWriter => constructorWriter.WriteAssignment("Kind", "kind") - ) - .WriteConstructor( - new("ActivityAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("name", PurviewTypeLibrary.System.String), - new("kind", TypeLibrary.Activities.SystemDiagnostics.ActivityKind.AsTypeReference()) - { - DefaultValue = "global::System.Diagnostics.ActivityKind.Internal", - }, - new("createOnly", PurviewTypeLibrary.System.Boolean) { DefaultValue = "false" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Name", "name") - .WriteAssignment("Kind", "kind") - .WriteAssignment("CreateOnly", "createOnly") - ); - - WriteStringProperty(body, "Name", nullable: true); - body.WriteProperty( - new( - "Kind", - TypeLibrary.Activities.SystemDiagnostics.ActivityKind.AsTypeReference(), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ); - body.WriteProperty( - new("CreateOnly", PurviewTypeLibrary.System.Boolean, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - } - ); - } - ); - - return writer.ToString(); - } - - static string EmitEventAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("EventAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Method, - body => - { - body.WriteConstructor( - new("EventAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new( - "statusCode", - TypeLibrary.Activities.SystemDiagnostics.ActivityStatusCode.AsTypeReference() - ) - { - DefaultValue = "global::System.Diagnostics.ActivityStatusCode.Unset", - }, - ], - }, - static constructorWriter => constructorWriter.WriteAssignment("StatusCode", "statusCode") - ) - .WriteConstructor( - new("EventAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("name", PurviewTypeLibrary.System.String), - new("useRecordExceptionRules", PurviewTypeLibrary.System.Boolean) - { - DefaultValue = "true", - }, - new("recordExceptionAsEscaped", PurviewTypeLibrary.System.Boolean) - { - DefaultValue = "true", - }, - new( - "statusCode", - TypeLibrary.Activities.SystemDiagnostics.ActivityStatusCode.AsTypeReference() - ) - { - DefaultValue = "global::System.Diagnostics.ActivityStatusCode.Unset", - }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Name", "name") - .WriteAssignment("UseRecordExceptionRules", "useRecordExceptionRules") - .WriteAssignment("RecordExceptionAsEscaped", "recordExceptionAsEscaped") - .WriteAssignment("StatusCode", "statusCode") - ); - - WriteStringProperty(body, "Name", nullable: true); - body.WriteProperty( - new( - "UseRecordExceptionRules", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ) - .WriteProperty( - new( - "RecordExceptionAsEscaped", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ) - .WriteProperty( - new( - "StatusCode", - TypeLibrary.Activities.SystemDiagnostics.ActivityStatusCode.AsTypeReference(), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ); - WriteStringProperty(body, "StatusDescription", nullable: true); - } - ); - - return writer.ToString(); - } - - static string EmitBaggageAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("BaggageAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Parameter, - body => - { - body.WriteConstructor( - new("BaggageAttribute", TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ) - .WriteConstructor( - new("BaggageAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = [new("skipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean)], - }, - static constructorWriter => - constructorWriter.WriteAssignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty") - ) - .WriteConstructor( - new("BaggageAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("name", PurviewTypeLibrary.System.String), - new("skipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean) { DefaultValue = "false" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Name", "name") - .WriteAssignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty") - ); - - WriteStringProperty(body, "Name", nullable: true); - body.WriteProperty( - new("SkipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - } - ); - } - ); - - return writer.ToString(); - } - - static string EmitTagAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("TagAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Parameter, - body => - { - body.WriteConstructor( - new("TagAttribute", TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ) - .WriteConstructor( - new("TagAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = [new("skipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean)], - }, - static constructorWriter => - constructorWriter.WriteAssignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty") - ) - .WriteConstructor( - new("TagAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("name", PurviewTypeLibrary.System.String), - new("skipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean) { DefaultValue = "false" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Name", "name") - .WriteAssignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty") - ); - - WriteStringProperty(body, "Name", nullable: true); - body.WriteProperty( - new("SkipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - } - ); - } - ); - - return writer.ToString(); - } - - static string EmitExcludeTargetsAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("ExcludeTargetsAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute()], - }, - AttributeTargets.Parameter, - body => - { - body.XmlSummary("Constructs a new instance with the specified targets to exclude.") - .WriteConstructor( - new("ExcludeTargetsAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("targets", new TypeReference(new TypeIdentity("Targets", "Purview.Telemetry"))), - ], - }, - static constructorWriter => constructorWriter.WriteAssignment("ExcludedTargets", "targets") - ); - - body.XmlSummary("Gets or sets the targets to exclude for this parameter.") - .WriteProperty( - new( - "ExcludedTargets", - new TypeReference(new TypeIdentity("Targets", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ); - } - ); - - return writer.ToString(); - } - - // --------------------------------------------------------------------------------------- - // Logging - // --------------------------------------------------------------------------------------- - - static string EmitTelemetryGenerationAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("TelemetryGenerationAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Assembly | AttributeTargets.Interface, - body => - { - body.WriteConstructor( - new("TelemetryGenerationAttribute", TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ) - .WriteConstructor( - new("TelemetryGenerationAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("generateDependencyExtension", PurviewTypeLibrary.System.Boolean), - new("className", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("dependencyInjectionClassName", PurviewTypeLibrary.System.String) - { - DefaultValue = "null", - }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("GenerateDependencyExtension", "generateDependencyExtension") - .WriteAssignment("ClassName", "className") - .WriteAssignment("DependencyInjectionClassName", "dependencyInjectionClassName") - ) - .WriteConstructor( - new("TelemetryGenerationAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("className", PurviewTypeLibrary.System.String), - new("dependencyInjectionClassName", PurviewTypeLibrary.System.String) - { - DefaultValue = "null", - }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("ClassName", "className") - .WriteAssignment("DependencyInjectionClassName", "dependencyInjectionClassName") - ); - - body.WriteProperty( - new( - "GenerateDependencyExtension", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ); - WriteStringProperty(body, "ClassName", nullable: true); - WriteStringProperty(body, "DependencyInjectionClassName", nullable: true); - body.WriteProperty( - new( - "DependencyInjectionClassIsPublic", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ) - .WriteProperty( - new( - "NamingConvention", - new TypeReference(new TypeIdentity("NamingConvention", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "global::Purview.Telemetry.NamingConvention.OpenTelemetry", - } - ) - .WriteProperty( - new( - "GenerateTelemetryNamesClass", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ); - WriteStringProperty(body, "TelemetryNamesClassName", nullable: true); - } - ); - - return writer.ToString(); - } - - static string EmitLoggerGenerationAttribute() - { - var writer = CreateWriter(logging: true); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("LoggerGenerationAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Assembly, - body => - { - body.WriteConstructor( - new("LoggerGenerationAttribute", TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ) - .WriteConstructor( - new("LoggerGenerationAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("defaultLevel", TypeLibrary.Logging.MicrosoftExtensions.LogLevel.AsTypeReference()), - ], - }, - static constructorWriter => constructorWriter.WriteAssignment("DefaultLevel", "defaultLevel") - ); - - body.WriteProperty( - new( - "DefaultLevel", - TypeLibrary.Logging.MicrosoftExtensions.LogLevel.AsTypeReference(), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "global::Microsoft.Extensions.Logging.LogLevel.Information", - } - ) - .WriteProperty( - new( - "GenerationMode", - new TypeReference(new TypeIdentity("LoggerGenerationMode", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ) - .WriteProperty( - new( - "DefaultPrefixType", - new TypeReference(new TypeIdentity("LogPrefixType", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ); - } - ); - - writer.WriteLine("#endif"); - return writer.ToString(); - } - - static string EmitLoggerAttribute() - { - var writer = CreateWriter(logging: true); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("LoggerAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Interface, - body => - { - body.WriteConstructor( - new("LoggerAttribute", TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ) - .WriteConstructor( - new("LoggerAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("defaultLevel", TypeLibrary.Logging.MicrosoftExtensions.LogLevel.AsTypeReference()), - new("customPrefix", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("DefaultLevel", "defaultLevel") - .WriteAssignment("CustomPrefix", "customPrefix") - .WriteLine("if (!string.IsNullOrWhiteSpace(CustomPrefix))") - .WriteBlock( - null, - inner => - inner.WriteAssignment( - "PrefixType", - "global::Purview.Telemetry.LogPrefixType.Custom" - ) - ) - ); - - body.WriteProperty( - new( - "DefaultLevel", - TypeLibrary.Logging.MicrosoftExtensions.LogLevel.AsTypeReference(), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "global::Microsoft.Extensions.Logging.LogLevel.Information", - } - ); - WriteStringProperty(body, "CustomPrefix", nullable: true); - body.WriteProperty( - new( - "PrefixType", - new TypeReference(new TypeIdentity("LogPrefixType", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ) - .WriteProperty( - new( - "GenerationMode", - new TypeReference(new TypeIdentity("LoggerGenerationMode", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ); - } - ); - - writer.WriteLine("#endif"); - return writer.ToString(); - } - - static string EmitLogAttribute() - { - var writer = CreateWriter(logging: true); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("LogAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Method, - body => - { - body.WriteConstructor( - new("LogAttribute", TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ) - .WriteConstructor( - new("LogAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = [new("messageTemplate", PurviewTypeLibrary.System.String)], - }, - static constructorWriter => - constructorWriter.WriteAssignment("MessageTemplate", "messageTemplate") - ) - .WriteConstructor( - new("LogAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = [new("eventId", PurviewTypeLibrary.System.Int32)], - }, - static constructorWriter => constructorWriter.WriteAssignment("EventId", "eventId") - ) - .WriteConstructor( - new("LogAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("level", TypeLibrary.Logging.MicrosoftExtensions.LogLevel.AsTypeReference()), - new("messageTemplate", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("name", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Level", "level") - .WriteAssignment("MessageTemplate", "messageTemplate") - .WriteAssignment("Name", "name") - ) - .WriteConstructor( - new("LogAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("eventId", PurviewTypeLibrary.System.Int32), - new("level", TypeLibrary.Logging.MicrosoftExtensions.LogLevel.AsTypeReference()), - new("messageTemplate", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("name", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Level", "level") - .WriteAssignment("MessageTemplate", "messageTemplate") - .WriteAssignment("EventId", "eventId") - .WriteAssignment("Name", "name") - ); - - body.WriteProperty( - new( - "Level", - TypeLibrary.Logging.MicrosoftExtensions.LogLevel.AsTypeReference(), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "global::Microsoft.Extensions.Logging.LogLevel.Information", - } - ); - WriteStringProperty(body, "MessageTemplate", nullable: true); - body.WriteProperty( - new("EventId", TypeReference.Create(), TypeDeclarationAccessibility.Public) - { - HasSetter = true, - } - ) - .WriteProperty( - new( - "Name", - PurviewTypeLibrary.System.String.MakeNullable(), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ) - .WriteProperty( - new( - "GenerationMode", - new TypeReference(new TypeIdentity("LoggerGenerationMode", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ); - } - ); - - writer.WriteLine("#endif"); - return writer.ToString(); - } - - static string EmitSpecificLogAttribute(string typeName) - { - var writer = CreateWriter(logging: true); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new(typeName, TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Method, - body => - { - body.WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public) - { - Parameters = [new("messageTemplate", PurviewTypeLibrary.System.String)], - }, - static constructorWriter => - constructorWriter.WriteAssignment("MessageTemplate", "messageTemplate") - ) - .WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public) - { - Parameters = [new("eventId", PurviewTypeLibrary.System.Int32)], - }, - static constructorWriter => constructorWriter.WriteAssignment("EventId", "eventId") - ) - .WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("messageTemplate", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("name", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("MessageTemplate", "messageTemplate") - .WriteAssignment("Name", "name") - ) - .WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("eventId", PurviewTypeLibrary.System.Int32), - new("messageTemplate", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("name", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("MessageTemplate", "messageTemplate") - .WriteAssignment("EventId", "eventId") - .WriteAssignment("Name", "name") - ); - - WriteStringProperty(body, "MessageTemplate", nullable: true); - body.WriteProperty( - new("EventId", TypeReference.Create(), TypeDeclarationAccessibility.Public) - { - HasSetter = true, - } - ); - WriteStringProperty(body, "Name", nullable: true); - body.WriteProperty( - new( - "GenerationMode", - new TypeReference(new TypeIdentity("LoggerGenerationMode", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ); - } - ); - - writer.WriteLine("#endif"); - return writer.ToString(); - } - - static string EmitExpandEnumerableAttribute() - { - var writer = CreateWriter(logging: true); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("ExpandEnumerableAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Parameter, - body => - { - body.WriteConstructor( - new("ExpandEnumerableAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = [new("maximumValueCount", PurviewTypeLibrary.System.Int32) { DefaultValue = "5" }], - }, - static constructorWriter => - constructorWriter.WriteAssignment("MaximumValueCount", "maximumValueCount") - ); - - body.XmlSummary("Gets or sets the maximum number of values to include when expanding an enumerable.") - .WriteProperty( - new("MaximumValueCount", PurviewTypeLibrary.System.Int32, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - } - ); - } - ); - - writer.WriteLine("#endif"); - return writer.ToString(); - } - - // --------------------------------------------------------------------------------------- - // Metrics - // --------------------------------------------------------------------------------------- - - static string EmitMeterGenerationAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("MeterGenerationAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Assembly, - body => - { - body.WriteConstructor( - new("MeterGenerationAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("meterName", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new( - "nameGenerationType", - new TypeReference(new TypeIdentity("MeterNameGenerationType", "Purview.Telemetry")) - ) - { - DefaultValue = "global::Purview.Telemetry.MeterNameGenerationType.DotNet", - }, - new("instrumentPrefix", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("lowercaseInstrumentName", PurviewTypeLibrary.System.Boolean) { DefaultValue = "true" }, - new("lowercaseTagKeys", PurviewTypeLibrary.System.Boolean) { DefaultValue = "true" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("MeterName", "meterName") - .WriteAssignment("MeterNameGenerationType", "nameGenerationType") - .WriteAssignment("InstrumentPrefix", "instrumentPrefix") - .WriteAssignment("LowercaseInstrumentName", "lowercaseInstrumentName") - .WriteAssignment("LowercaseTagKeys", "lowercaseTagKeys") - ); - - WriteStringProperty(body, "MeterName", nullable: true); - body.WriteProperty( - new( - "MeterNameGenerationType", - new TypeReference(new TypeIdentity("MeterNameGenerationType", "Purview.Telemetry")), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "global::Purview.Telemetry.MeterNameGenerationType.DotNet", - } - ); - WriteStringProperty(body, "InstrumentPrefix", nullable: true); - WriteProperty(body, "string", "InstrumentSeparator", false, true, "\".\""); - body.WriteProperty( - new( - "LowercaseInstrumentName", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ) - .WriteProperty( - new("LowercaseTagKeys", PurviewTypeLibrary.System.Boolean, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - Initializer = "true", - } - ); - } - ); - - return writer.ToString(); - } - - static string EmitMeterAttribute() - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new("MeterAttribute", TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Interface, - body => - { - body.WriteConstructor( - new("MeterAttribute", TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ) - .WriteConstructor( - new("MeterAttribute", TypeDeclarationAccessibility.Public) - { - Parameters = [new("name", PurviewTypeLibrary.System.String)], - }, - static constructorWriter => constructorWriter.WriteAssignment("Name", "name") - ); - - WriteStringProperty(body, "Name", nullable: true); - WriteStringProperty(body, "InstrumentPrefix", nullable: true); - body.WriteProperty( - new( - "IncludeAssemblyInstrumentPrefix", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ) - .WriteProperty( - new( - "LowercaseInstrumentName", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - Initializer = "true", - } - ) - .WriteProperty( - new("LowercaseTagKeys", PurviewTypeLibrary.System.Boolean, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - Initializer = "true", - } - ); - } - ); - - return writer.ToString(); - } - - static string EmitInstrumentAttribute(string typeName, bool autoIncrement, bool observable) - { - var writer = CreateWriter(); - writer.WriteFileScopedNamespace("Purview.Telemetry"); - writer.WriteAttributeClass( - new(typeName, TypeDeclarationAccessibility.Internal) - { - IsSealed = true, - Attributes = [ConditionalAttribute(), SuppressMessageAttribute()], - }, - AttributeTargets.Method, - body => - { - body.WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public), - static w => w.Comment("Empty") - ); - - if (autoIncrement) - { - body.WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("name", PurviewTypeLibrary.System.String), - new("unit", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("description", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Name", "name") - .WriteAssignment("Unit", "unit") - .WriteAssignment("Description", "description") - ); - } - else if (observable) - { - body.WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("name", PurviewTypeLibrary.System.String), - new("unit", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("description", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("throwOnAlreadyInitialized", PurviewTypeLibrary.System.Boolean) - { - DefaultValue = "false", - }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Name", "name") - .WriteAssignment("Unit", "unit") - .WriteAssignment("Description", "description") - .WriteAssignment("ThrowOnAlreadyInitialized", "throwOnAlreadyInitialized") - ); - } - else - { - body.WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public) - { - Parameters = [new("autoIncrement", PurviewTypeLibrary.System.Boolean)], - }, - static constructorWriter => constructorWriter.WriteAssignment("AutoIncrement", "autoIncrement") - ); - body.WriteConstructor( - new(typeName, TypeDeclarationAccessibility.Public) - { - Parameters = - [ - new("name", PurviewTypeLibrary.System.String), - new("unit", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("description", PurviewTypeLibrary.System.String) { DefaultValue = "null" }, - new("autoIncrement", PurviewTypeLibrary.System.Boolean) { DefaultValue = "false" }, - ], - }, - static constructorWriter => - constructorWriter - .WriteAssignment("Name", "name") - .WriteAssignment("Unit", "unit") - .WriteAssignment("Description", "description") - .WriteAssignment("AutoIncrement", "autoIncrement") - ); - } - - if (!autoIncrement) - body.WriteProperty( - new("AutoIncrement", PurviewTypeLibrary.System.Boolean, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - } - ); - - WriteStringProperty(body, "Name", nullable: true); - WriteStringProperty(body, "Unit", nullable: true); - WriteStringProperty(body, "Description", nullable: true); - - if (observable) - body.WriteProperty( - new( - "ThrowOnAlreadyInitialized", - PurviewTypeLibrary.System.Boolean, - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - } - ); - } - ); - - return writer.ToString(); - } -} diff --git a/src/src/SourceGenerator/Templates/TemplateInfo.cs b/src/src/SourceGenerator/Templates/TemplateInfo.cs index 7eb2c5c2..b6eb51cb 100644 --- a/src/src/SourceGenerator/Templates/TemplateInfo.cs +++ b/src/src/SourceGenerator/Templates/TemplateInfo.cs @@ -1,6 +1,11 @@ namespace Purview.Telemetry.SourceGenerator.Templates; -sealed record TemplateInfo(TypeIdentity TypeInfo, string? Source, string TemplateData) : IEquatable +/// +/// Identity of a marker-attribute template. The template source is not held in memory; +/// it is emitted with a during RegisterPostInitializationOutput +/// (see MarkerAttributeTemplateEmitter). +/// +sealed record TemplateInfo(TypeIdentity TypeInfo) : IEquatable { public string Name => TypeInfo.Name; @@ -8,25 +13,7 @@ sealed record TemplateInfo(TypeIdentity TypeInfo, string? Source, string Templat public bool Equals(TypeIdentity other) => other.Equals(TypeInfo); - public static TemplateInfo Create(string fullTypeName) - { - var lastDotIndex = fullTypeName.LastIndexOf('.'); - var typeName = fullTypeName.Substring(lastDotIndex + 1); - var @namespace = fullTypeName.Substring(0, lastDotIndex); - - return Create(new TypeIdentity(typeName, @namespace)); - } - - public static TemplateInfo Create(TypeIdentity type) - { - var source = type.Namespace?.Split('.') ?? []; - var isRootSources = source.Length == 2; - var sourceToUse = isRootSources ? null : source.LastOrDefault(); - - var template = SourceEmitter.Emit(type.Name); - - return new(type, sourceToUse, template); - } + public static TemplateInfo Create(TypeIdentity type) => new(type); public static implicit operator string(TemplateInfo templateInfo) => templateInfo.TypeInfo.RenderFullName; } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorCachingTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorCachingTests.cs index 5d4286ff..3ac888ab 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorCachingTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorCachingTests.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Generators; @@ -46,7 +47,7 @@ public class Unrelated { public int Value { get; set; } } static ImmutableArray BuildReferences() { var trusted = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") ?? "") - .Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries) + .Split([Path.PathSeparator], StringSplitOptions.RemoveEmptyEntries) .Select(path => MetadataReference.CreateFromFile(path)) .ToList(); @@ -113,8 +114,8 @@ public async Task Generate_UnchangedCompilation_OutputIsStable(CancellationToken var secondSources = GetGeneratedSources(second); await Assert.That(secondSources.Count).IsEqualTo(firstSources.Count); - foreach (var (hintName, text) in firstSources) - await Assert.That(secondSources[hintName]).IsEqualTo(text); + foreach (var source in firstSources) + await Assert.That(secondSources[source.Key]).IsEqualTo(source.Value); } [Test] @@ -132,8 +133,8 @@ public async Task Generate_UnrelatedChange_AllOutputUnchanged(CancellationToken var runSources = GetGeneratedSources(run); await Assert.That(runSources.Count).IsEqualTo(firstSources.Count); - foreach (var (hintName, text) in firstSources) - await Assert.That(runSources[hintName]).IsEqualTo(text); + foreach (var source in firstSources) + await Assert.That(runSources[source.Key]).IsEqualTo(source.Value); } [Test] @@ -147,14 +148,16 @@ public async Task Generate_LoggerEdit_OnlyLoggerOutputChanges(CancellationToken var firstSources = GetGeneratedSources(first); // Edit only the logger interface. - var editedLogger = LoggerInterface.Replace("void Log(", "void Log2(", StringComparison.Ordinal); + var editedLogger = LoggerInterface.ReplaceOrdinal("void Log(", "void Log2("); var edited = CreateCompilation(ActivityInterface, editedLogger); var run = driver.RunGeneratorsAndUpdateCompilation(edited, out _, out _, cancellationToken).GetRunResult(); var runSources = GetGeneratedSources(run); await Assert.That(runSources.Count).IsEqualTo(firstSources.Count); - foreach (var (hintName, text) in firstSources) + foreach (var source in firstSources) { + var hintName = source.Key; + var text = source.Value; if ( !hintName.EndsWith(".Activity.g.cs", StringComparison.Ordinal) && !hintName.EndsWith(".Logging.g.cs", StringComparison.Ordinal) diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs index 410d22d6..31230458 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs @@ -2,6 +2,7 @@ namespace Purview.Telemetry.SourceGenerator.Metrics; +[SkipOnNetFramework] public partial class TelemetrySourceGeneratorMetricsTests : IncrementalSourceGeneratorTestBase { [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/SkipOnNetFrameworkAttribute.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/SkipOnNetFrameworkAttribute.cs new file mode 100644 index 00000000..d335909a --- /dev/null +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/SkipOnNetFrameworkAttribute.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Purview.Telemetry.SourceGenerator.Infra; + +/// +/// Skips a test (or test class) when running on .NET Framework, where APIs such as +/// System.Diagnostics.Metrics are unavailable. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false)] +sealed class SkipOnNetFrameworkAttribute : SkipAttribute +{ + public SkipOnNetFrameworkAttribute() + : base("This test requires .NET 6+ APIs that are unavailable on .NET Framework.") { } + + public override Task ShouldSkip(TestRegisteredContext context) => + Task.FromResult(RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.Ordinal)); +} diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs index a0db5315..6f3aed63 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs @@ -1,13 +1,10 @@ -using System.Reflection; -using System.Text; +using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Purview.Telemetry.SourceGenerator.Infra; static class TestHelpers { - static readonly Assembly OwnerAssembly = typeof(TestHelpers).Assembly; - static readonly string NamespaceRoot = typeof(TestHelpers).Namespace!; - public const string DefaultUsingSet = @" using System; @@ -17,22 +14,27 @@ static class TestHelpers public static string Wrap(this string value, char c = '"') => c + value + c; - public static string LoadEmbeddedResource(string folder, string resourceName) + /// Replaces all occurrences using ordinal semantics (net48 lacks the StringComparison overload). + public static string ReplaceOrdinal(this string value, string oldValue, string newValue) => +#if NET48 + value.Replace(oldValue, newValue); +#else + value.Replace(oldValue, newValue, StringComparison.Ordinal); +#endif + + /// Throws if the argument is null (net48 lacks ArgumentNullException.ThrowIfNull). +#if NET8_0_OR_GREATER + [StackTraceHidden] +#endif + [DebuggerHidden] + public static void ThrowIfNull(object? argument, [CallerMemberName] string? paramName = null) { - resourceName = $"{NamespaceRoot}.Resources.{folder}.{resourceName}"; - - var resourceStream = OwnerAssembly.GetManifestResourceStream(resourceName); - if (resourceStream is null) - { - var existingResources = OwnerAssembly.GetManifestResourceNames(); - throw new ArgumentException( - $"Could not find embedded resource {resourceName}. Available resource names: {string.Join(", ", existingResources)}" - ); - } - - using StreamReader reader = new(resourceStream, Encoding.UTF8); - - return reader.ReadToEnd(); +#if NET48 + if (argument is null) + throw new ArgumentNullException(paramName); +#else + ArgumentNullException.ThrowIfNull(argument, paramName); +#endif } public static List GetCasePermutations(string input) @@ -46,7 +48,7 @@ public static List GetCasePermutations(string input) } var currentChar = input[0]; - var remainder = input[1..]; + var remainder = input.Substring(1); var remainderPermutations = GetCasePermutations(remainder); if (char.IsLetter(currentChar)) @@ -110,7 +112,7 @@ await Assert if (!validationCompilation) return; - await using MemoryStream ms = new(); + using MemoryStream ms = new(); var emitResult = result.CompilationResult.Compilation.Emit(ms, cancellationToken: cancellationToken); if (!emitResult.Success) diff --git a/src/tests/SourceGenerator.IntegrationTests/Polyfills/Net48Polyfills.cs b/src/tests/SourceGenerator.IntegrationTests/Polyfills/Net48Polyfills.cs new file mode 100644 index 00000000..409bff98 --- /dev/null +++ b/src/tests/SourceGenerator.IntegrationTests/Polyfills/Net48Polyfills.cs @@ -0,0 +1,26 @@ +#if NET48 + +using System.ComponentModel; + +// net48 lacks ModuleInitializerAttribute (net5+) and declares NotNullAttribute as internal. +// The TUnit infrastructure source generator emits [ModuleInitializer], and the tests use [NotNull]; +// public polyfills are required so those compile on net48. +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Runtime.CompilerServices +{ + [EditorBrowsable(EditorBrowsableState.Never)] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + sealed class ModuleInitializerAttribute : Attribute { } +} +#pragma warning restore IDE0130 + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Diagnostics.CodeAnalysis +{ + [EditorBrowsable(EditorBrowsableState.Never)] + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, Inherited = false)] + sealed class NotNullAttribute : Attribute { } +} +#pragma warning restore IDE0130 + +#endif diff --git a/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs b/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs index 2e1dc773..d162c242 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs @@ -1,6 +1,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp; +using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Refactorings; @@ -98,8 +99,8 @@ protected static async Task> GetRefactoringActionsAsyn CancellationToken cancellationToken = default ) { - ArgumentNullException.ThrowIfNull(codeWithMarker); - ArgumentNullException.ThrowIfNull(provider); + TestHelpers.ThrowIfNull(codeWithMarker); + TestHelpers.ThrowIfNull(provider); const string marker = "$$"; @@ -143,14 +144,42 @@ protected static async Task> GetRefactoringActionsAsyn static IEnumerable GetDefaultReferences() { yield return MetadataReference.CreateFromFile(typeof(object).Assembly.Location); - yield return MetadataReference.CreateFromFile( - System.Reflection.Assembly.Load("netstandard, Version=2.0.0.0").Location - ); - yield return MetadataReference.CreateFromFile(System.Reflection.Assembly.Load("System.Runtime").Location); + + // netstandard/System.Runtime facades are not available on .NET Framework. + if (TryLoadAssembly("netstandard, Version=2.0.0.0", out var netstandard)) + yield return MetadataReference.CreateFromFile(netstandard.Location); + if (TryLoadAssembly("System.Runtime", out var systemRuntime)) + yield return MetadataReference.CreateFromFile(systemRuntime.Location); + yield return MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Logging.ILogger).Assembly.Location); yield return MetadataReference.CreateFromFile(typeof(Microsoft.Extensions.Logging.LogLevel).Assembly.Location); yield return MetadataReference.CreateFromFile(typeof(System.Diagnostics.ActivitySource).Assembly.Location); - yield return MetadataReference.CreateFromFile(typeof(System.Diagnostics.Metrics.Counter<>).Assembly.Location); + + // System.Diagnostics.Metrics is not available on .NET Framework. + var counterType = typeof(System.Diagnostics.ActivitySource).Assembly.GetType( + "System.Diagnostics.Metrics.Counter`1" + ); + if (counterType is not null) + yield return MetadataReference.CreateFromFile(counterType.Assembly.Location); + } + + static bool TryLoadAssembly(string name, out System.Reflection.Assembly assembly) + { + try + { + assembly = System.Reflection.Assembly.Load(name); + return true; + } + catch (FileNotFoundException) + { + assembly = null!; + return false; + } + catch (FileLoadException) + { + assembly = null!; + return false; + } } /// @@ -174,8 +203,8 @@ protected static async Task VerifyRefactoringAsync( CancellationToken cancellationToken = default ) { - ArgumentNullException.ThrowIfNull(codeWithMarker); - ArgumentNullException.ThrowIfNull(provider); + TestHelpers.ThrowIfNull(codeWithMarker, nameof(codeWithMarker)); + TestHelpers.ThrowIfNull(provider, nameof(provider)); var after = await ApplyRefactoringAsync(codeWithMarker, provider, equivalenceKey, cancellationToken); @@ -183,7 +212,7 @@ protected static async Task VerifyRefactoringAsync( await Assert .That(after) - .IsNotEqualTo(codeWithMarker.Replace("$$", string.Empty, StringComparison.Ordinal).TrimStart()) + .IsNotEqualTo(codeWithMarker.ReplaceOrdinal("$$", string.Empty).TrimStart()) .Because("Expected the refactoring to change the source."); var parseResult = SyntaxFactory.ParseSyntaxTree(after!, cancellationToken: cancellationToken); diff --git a/src/tests/SourceGenerator.IntegrationTests/Refactorings/ConvertMetricsToTelemetryRefactoringProviderSnapshotTests.cs b/src/tests/SourceGenerator.IntegrationTests/Refactorings/ConvertMetricsToTelemetryRefactoringProviderSnapshotTests.cs index 7f878421..c39a7265 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Refactorings/ConvertMetricsToTelemetryRefactoringProviderSnapshotTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Refactorings/ConvertMetricsToTelemetryRefactoringProviderSnapshotTests.cs @@ -1,3 +1,5 @@ +using Purview.Telemetry.SourceGenerator.Infra; + namespace Purview.Telemetry.SourceGenerator.Refactorings; /// @@ -5,6 +7,7 @@ namespace Purview.Telemetry.SourceGenerator.Refactorings; /// Each test defines a before scenario and the snapshot captures the after output. /// To regenerate snapshots: run dotnet test; *.received.txt files are auto-accepted. /// +[SkipOnNetFramework] public sealed class ConvertMetricsToTelemetryRefactoringProviderSnapshotTests : CodeRefactoringTestBase { static readonly ConvertMetricsToTelemetryRefactoringProvider Provider = new(); diff --git a/src/tests/SourceGenerator.IntegrationTests/Refactorings/ConvertMetricsToTelemetryRefactoringProviderTests.cs b/src/tests/SourceGenerator.IntegrationTests/Refactorings/ConvertMetricsToTelemetryRefactoringProviderTests.cs index a24dc5ed..beb4fbd3 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Refactorings/ConvertMetricsToTelemetryRefactoringProviderTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Refactorings/ConvertMetricsToTelemetryRefactoringProviderTests.cs @@ -1,5 +1,8 @@ +using Purview.Telemetry.SourceGenerator.Infra; + namespace Purview.Telemetry.SourceGenerator.Refactorings; +[SkipOnNetFramework] public sealed class ConvertMetricsToTelemetryRefactoringProviderTests : CodeRefactoringTestBase { static readonly ConvertMetricsToTelemetryRefactoringProvider Provider = new(); diff --git a/src/tests/SourceGenerator.IntegrationTests/SourceGenerator.IntegrationTests.csproj b/src/tests/SourceGenerator.IntegrationTests/SourceGenerator.IntegrationTests.csproj index ad665119..505283a6 100644 --- a/src/tests/SourceGenerator.IntegrationTests/SourceGenerator.IntegrationTests.csproj +++ b/src/tests/SourceGenerator.IntegrationTests/SourceGenerator.IntegrationTests.csproj @@ -1,11 +1,13 @@  true + + + + net8.0;net9.0;net10.0; + net48;$(TargetFrameworks) - - - - @@ -21,4 +23,27 @@ + + + true + + + + + + + + + + + + + + + + diff --git a/src/tests/SourceGenerator.IntegrationTests/TelemetrySourceGeneratorCachingTests.cs b/src/tests/SourceGenerator.IntegrationTests/TelemetrySourceGeneratorCachingTests.cs index 2d2e37d2..5b0431fc 100644 --- a/src/tests/SourceGenerator.IntegrationTests/TelemetrySourceGeneratorCachingTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/TelemetrySourceGeneratorCachingTests.cs @@ -3,6 +3,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator; @@ -49,10 +50,21 @@ public class Unrelated { public int Value { get; set; } } static ImmutableArray BuildReferences() { var trusted = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") ?? "") - .Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries) + .Split([Path.PathSeparator], StringSplitOptions.RemoveEmptyEntries) .Select(path => MetadataReference.CreateFromFile(path)) .ToList(); + // .NET Framework does not populate TRUSTED_PLATFORM_ASSEMBLIES. + if (trusted.Count == 0) + { + trusted.AddRange( + AppDomain + .CurrentDomain.GetAssemblies() + .Where(static assembly => !assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location)) + .Select(static assembly => MetadataReference.CreateFromFile(assembly.Location)) + ); + } + foreach ( var type in new[] { @@ -92,13 +104,15 @@ static CSharpGeneratorDriver CreateDriver() => static ImmutableArray GetSourceOutputReasons(GeneratorDriverRunResult result) { - return result - .Results.SelectMany(static r => r.TrackedSteps) - .Where(static kvp => kvp.Key == "SourceOutput") - .SelectMany(static kvp => kvp.Value) - .SelectMany(static runStep => runStep.Outputs) - .Select(static o => o.Reason) - .ToImmutableArray(); + return + [ + .. result + .Results.SelectMany(static r => r.TrackedSteps) + .Where(static kvp => kvp.Key == "SourceOutput") + .SelectMany(static kvp => kvp.Value) + .SelectMany(static runStep => runStep.Outputs) + .Select(static o => o.Reason), + ]; } [Test] @@ -159,9 +173,7 @@ public async Task Generate_ActivityEdit_OutputReRuns(CancellationToken cancellat driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken); // Edit the activity interface source (change the method name so the output changes). - var edited = CreateCompilation( - ActivityInterface.Replace("Activity([Tag]", "Activity2([Tag]", StringComparison.Ordinal) - ); + var edited = CreateCompilation(ActivityInterface.ReplaceOrdinal("Activity([Tag]", "Activity2([Tag]")); driver = driver.RunGeneratorsAndUpdateCompilation(edited, out _, out _, cancellationToken); var reasons = GetSourceOutputReasons(driver.GetRunResult()); await Assert From 5edcb195044d4ff75a1859ec7d96571785575c00 Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Wed, 2 Sep 2026 18:32:03 +0100 Subject: [PATCH 2/3] refactor: mass refactor to support updated source gen package --- Justfile | 37 +- .../Analyzers/TelemetryDiagnosticAnalyzer.cs | 10 +- ...ourceTargetClassEmitter.ActivityMethods.cs | 144 +++--- ...SourceTargetClassEmitter.ContextMethods.cs | 22 +- ...tySourceTargetClassEmitter.EventMethods.cs | 34 +- ...ActivitySourceTargetClassEmitter.Fields.cs | 10 +- ...ctivitySourceTargetClassEmitter.Methods.cs | 123 ++--- ...vitySourceTargetClassEmitter.Parameters.cs | 19 +- .../ActivitySourceTargetClassEmitter.cs | 28 +- .../Emitters/ConstructorEmitter.cs | 29 +- .../DependencyInjectionClassEmitter.cs | 81 +++- .../Emitters/EmitterHelpers.cs | 29 +- ...ateEmitter.cs => GeneratedTypesEmitter.cs} | 456 +++++++++--------- .../LoggerGenTargetClassEmitter.Methods.cs | 243 ++++------ .../Emitters/LoggerGenTargetClassEmitter.cs | 55 +-- .../LoggerTargetClassEmitter.Fields.cs | 36 +- .../LoggerTargetClassEmitter.Methods.cs | 53 +- .../Emitters/LoggerTargetClassEmitter.cs | 36 +- .../MeterTargetClassEmitter.Fields.cs | 13 +- ...TargetClassEmitter.InitializationMethod.cs | 42 +- .../MeterTargetClassEmitter.Methods.cs | 42 +- .../Emitters/MeterTargetClassEmitter.cs | 57 +-- .../Emitters/TelemetryNamesEmitter.cs | 31 +- .../TelemetrySourceGenerator.Activities.cs | 3 +- .../TelemetrySourceGenerator.Logging.cs | 6 +- .../TelemetrySourceGenerator.Metrics.cs | 2 +- ...TelemetrySourceGenerator.TelemetryNames.cs | 19 +- .../Generators/TelemetrySourceGenerator.cs | 29 +- .../Helpers/DiagnosticLibrary.Activities.cs | 377 +++++++++------ .../Helpers/DiagnosticLibrary.General.cs | 188 +++++--- .../Helpers/DiagnosticLibrary.Logging.cs | 151 +++--- .../Helpers/DiagnosticLibrary.Metrics.cs | 151 +++--- .../Helpers/DiagnosticLibrary.cs | 34 +- .../Helpers/InstrumentMethodModelBuilder.cs | 69 +-- .../Helpers/LogMethodModelBuilder.cs | 34 +- .../Helpers/PipelineHelpers.Activities.cs | 111 +---- .../Helpers/PipelineHelpers.Logger.cs | 27 +- .../Helpers/PipelineHelpers.Metrics.cs | 23 +- .../Helpers/PropertyLibrary.cs | 72 +-- .../Helpers/SharedHelpers.Activities.cs | 19 +- .../Helpers/SharedHelpers.Logging.cs | 101 +--- .../Helpers/SharedHelpers.Metrics.cs | 89 +--- .../SourceGenerator/Helpers/SharedHelpers.cs | 12 +- .../Helpers/TemplateLibrary.cs | 201 -------- .../SourceGenerator/Helpers/TypeLibrary.cs | 328 +++++++++---- src/src/SourceGenerator/Helpers/Utilities.cs | 111 ++--- .../Records/ActivityRecords.cs | 34 +- .../Records/AttributeDataModels.cs | 99 ++-- .../Records/GenericAttributeRecords.cs | 6 +- .../SourceGenerator/Records/GenericRecords.cs | 4 +- .../SourceGenerator/Records/LoggerRecords.cs | 38 +- .../SourceGenerator/Records/MetricRecords.cs | 34 +- .../SourceGenerator/Records/OutputContexts.cs | 20 +- .../Records/TelemetryCapabilities.cs | 6 +- .../Records/TelemetryDiagnosticDescriptor.cs | 12 - .../Records/TelemetryRules.Activities.cs | 46 +- .../Records/TelemetryRules.Logging.cs | 26 +- .../Records/TelemetryRules.Metrics.cs | 20 +- .../SourceGenerator/Records/TelemetryRules.cs | 163 +++---- .../SourceGenerator/Templates/TemplateInfo.cs | 19 - ...rceGeneratorActivityTests.BasicEventGen.cs | 14 +- ...rySourceGeneratorActivityTests.BasicGen.cs | 35 +- ...ourceGeneratorLoggingGen2Tests.CSharp73.cs | 2 +- ...eratorLoggingGen2Tests.ExpandEnumerable.cs | 7 +- ...ceGeneratorLoggingGen2Tests.PerMethodV1.cs | 7 +- ...trySourceGeneratorLoggingTests.BasicGen.cs | 14 +- ...trySourceGeneratorLoggingTests.CSharp73.cs | 2 +- ...trySourceGeneratorMetricsTests.CSharp73.cs | 2 +- ...trySourceGeneratorMetricsTests.Counters.cs | 21 +- .../TelemetrySourceGeneratorMetricsTests.cs | 14 +- ...lemetrySourceGeneratorTests.Docs_README.cs | 7 +- ...urceGeneratorTests.DuplicateMethodNames.cs | 35 +- ...SourceGeneratorTests.InvalidReturnTypes.cs | 136 +----- ...trySourceGeneratorTests.MultiGeneration.cs | 57 +-- ...etrySourceGeneratorTests.TelemetryBasic.cs | 28 +- ...eGeneratorTests.TelemetryNamesNamespace.cs | 100 ++++ .../TelemetrySourceGeneratorTestOptions.cs | 11 + .../Infra/TestHelpers.cs | 66 +-- .../Refactorings/CodeRefactoringTestBase.cs | 8 +- 79 files changed, 2093 insertions(+), 2787 deletions(-) rename src/src/SourceGenerator/Emitters/{MarkerAttributeTemplateEmitter.cs => GeneratedTypesEmitter.cs} (70%) delete mode 100644 src/src/SourceGenerator/Helpers/TemplateLibrary.cs delete mode 100644 src/src/SourceGenerator/Records/TelemetryDiagnosticDescriptor.cs delete mode 100644 src/src/SourceGenerator/Templates/TemplateInfo.cs create mode 100644 src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryNamesNamespace.cs diff --git a/Justfile b/Justfile index b37657d1..cb8b8d78 100644 --- a/Justfile +++ b/Justfile @@ -58,16 +58,21 @@ pipeline-tests *args: # Builds the solution with the specified configuration (default: Release) [group('Build and Test')] -build: +build *args: echo "Building {{ BLUE }}{{ solution_file }}{{ NORMAL }} with {{ YELLOW }}{{ build_configuration }}{{ NORMAL }}..." - dotnet build "{{ solution_file }}" --configuration "{{ build_configuration }}" + dotnet build "{{ solution_file }}" --configuration "{{ build_configuration }}" {{ args }} # Runs tests for the solution with the specified configuration (default: Release) - [group('Build and Test')] -test: +test filter="/*/*/*/*/" *args: echo "Running tests for {{ BLUE }}{{ test_solution }}{{ NORMAL }} with {{ YELLOW }}{{ build_configuration }}{{ NORMAL }}..." - dotnet test --solution "{{ test_solution }}" --configuration "{{ build_configuration }}" + dotnet test --solution "{{ test_solution }}" --configuration "{{ build_configuration }}" --treenode-filter={{ filter }} {{ args }} + +# Cleans the solution with the specified configuration (default: Release) +[group('Build and Test')] +clean *args: + echo "Cleaning {{ BLUE }}{{ solution_file }}{{ NORMAL }} with {{ YELLOW }}{{ build_configuration }}{{ NORMAL }}..." + dotnet clean "{{ solution_file }}" --configuration "{{ build_configuration }}" {{ args }} # Packs the source generator into a NuGet package @@ -111,40 +116,22 @@ test-s: # ----------------------------------------------------------------------------- # Formats the code in the root folder - format: echo "Formatting {{ BLUE }}{{ root_folder }}{{ NORMAL }}..." dotnet format "{{ root_folder }}" +# Checks for linting issues in the root folder lint: echo "Linting checking {{ BLUE }}{{ root_folder }}{{ NORMAL }}..." dotnet csharpier check . +# Fixes linting issues in the root folder lint-fix: echo "Linting fixing {{ BLUE }}{{ root_folder }}{{ NORMAL }}..." dotnet csharpier format . -# ----------------------------------------------------------------------------- -# Versioning and Release -# ----------------------------------------------------------------------------- - -# Creates a new changeset to describe the changes in the current branch. -# Requires Bun. - -[group('Versioning and Release')] -changeset: - bun changeset - -# Shows pending changesets and the version bump they imply. -# Requires Bun. - -[group('Versioning and Release')] -changeset-status: - bun changeset status - # Displays the current version of the project. # Requires Bun. - [group('Versioning and Release')] version: bun -e "console.log('Current Version: {{ GREEN }}' + require('./package.json').version + '{{ NORMAL }}')" diff --git a/src/src/SourceGenerator/Analyzers/TelemetryDiagnosticAnalyzer.cs b/src/src/SourceGenerator/Analyzers/TelemetryDiagnosticAnalyzer.cs index 333207de..ef6ee575 100644 --- a/src/src/SourceGenerator/Analyzers/TelemetryDiagnosticAnalyzer.cs +++ b/src/src/SourceGenerator/Analyzers/TelemetryDiagnosticAnalyzer.cs @@ -38,11 +38,11 @@ static void AnalyzeNamedType(SymbolAnalysisContext context) var hasActivitySource = Utilities.ContainsAttribute( interfaceSymbol, - TemplateLibrary.Activities.ActivitySourceAttribute, + TypeLibrary.Activities.ActivitySourceAttribute, token ); - var hasLogger = Utilities.ContainsAttribute(interfaceSymbol, TemplateLibrary.Logging.LoggerAttribute, token); - var hasMeter = Utilities.ContainsAttribute(interfaceSymbol, TemplateLibrary.Metrics.MeterAttribute, token); + var hasLogger = Utilities.ContainsAttribute(interfaceSymbol, TypeLibrary.Logging.LoggerAttribute, token); + var hasMeter = Utilities.ContainsAttribute(interfaceSymbol, TypeLibrary.Metrics.MeterAttribute, token); if (!hasActivitySource && !hasLogger && !hasMeter) return; @@ -65,7 +65,7 @@ static void AnalyzeNamedType(SymbolAnalysisContext context) if (hasActivitySource) { - var result = PipelineHelpers.BuildActivityTarget(interfaceSymbol, compilation, null, token); + var result = PipelineHelpers.BuildActivityTarget(interfaceSymbol, compilation, token); if (result.HasValue && result.Value is { } activityTarget) { var diagnostics = TelemetryRules.GetActivityDiagnostics(activityTarget, interfaceSymbol, token); @@ -76,7 +76,7 @@ static void AnalyzeNamedType(SymbolAnalysisContext context) if (hasMeter) { - var result = PipelineHelpers.BuildMeterTarget(interfaceSymbol, compilation, null, token); + var result = PipelineHelpers.BuildMeterTarget(interfaceSymbol, compilation, token); if (result.HasValue && result.Value is { } meterTarget) { var diagnostics = TelemetryRules.GetMeterDiagnostics(meterTarget, interfaceSymbol, token); diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ActivityMethods.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ActivityMethods.cs index 47aef968..f2e7c4fd 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ActivityMethods.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ActivityMethods.cs @@ -7,11 +7,10 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class ActivitySourceTargetClassEmitter { static void EmitActivityMethodBody( - CodeWriter writer, + ActivityOutputContext output, ActivityBasedGenerationTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable + CodeWriter writer, + SourceProductionContext context ) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -19,8 +18,7 @@ bool emitNullable if ( !GuardParameters( methodTarget, - context, - logger, + output, out var activityParam, out var parentContextOrId, out var tagsParam, @@ -37,69 +35,71 @@ out var _ if (activityParam != null) { - logger?.Diagnostic("Activity parameter not allowed on Activity start/ create method, only event."); - + output.Context.Diagnostic("Activity parameter not allowed on Activity start/ create method, only event."); return; } if (timestampParam != null) { - logger?.Diagnostic("Timestamp parameter not allowed on Activity start/ create method, only events."); - + output.Context.Diagnostic("Timestamp parameter not allowed on Activity start/ create method, only events."); return; } - EmitHasListenersTest(writer, methodTarget, emitNullable); + EmitHasListenersTest(writer, methodTarget); var activityVariableName = "activity" + methodTarget.MethodName; - writer - .Write(TypeLibrary.Activities.SystemDiagnostics.Activity) - .Write(emitNullable ? "? " : " ") - .Write(activityVariableName) - .Write(" = ") - .Write(PropertyLibrary.Activities.ActivitySourceFieldName) - .Write('.'); - - var createOnly = methodTarget.ActivityAttribute?.CreateOnly == true; - var useParentContext = - parentContextOrId != null - && parentContextOrId.ParameterType.Identity.Equals( - TypeLibrary.Activities.SystemDiagnostics.ActivityContext - ); - var parentContextParameterName = useParentContext ? "parentContext" : "parentId"; - - if (createOnly && startTimeParam != null) - { - logger?.Diagnostic("StartTime parameter not allowed on Activity create method."); - - return; - } - - var kind = methodTarget.ActivityAttribute?.Kind ?? PropertyLibrary.Activities.DefaultActivityKind; - - var parentContextOrIdParameterValue = parentContextOrId?.ParameterName ?? "default"; - if (useParentContext && parentContextOrId!.ParameterType.IsNullable) - { - // parentContextOrId is not going to be null at this point as - // we already checked the type. - // If it's nullable we need to use the null-coalescing operator... - // and we need to ensure its explicit or the call is ambiguous - // between ActivityContext and ParentId. - parentContextOrIdParameterValue += " ?? default"; - } - - EmitActivityCall( - writer, - methodTarget, - createOnly, - useParentContext, - parentContextParameterName, - parentContextOrIdParameterValue, - tagsParam, - linksParam, - startTimeParam, - kind + writer.WriteAssignment( + TypeLibrary.Activities.SystemDiagnostics.Activity.MakeNullable(writer), + activityVariableName, + writeValue: assignmentWriter => + { + assignmentWriter + //.Write(" = ") + .Write(PropertyLibrary.Activities.ActivitySourceFieldName) + .Write('.'); + + var createOnly = methodTarget.ActivityAttribute?.CreateOnly == true; + var useParentContext = + parentContextOrId != null + && parentContextOrId.ParameterType.Identity.Equals( + TypeLibrary.Activities.SystemDiagnostics.ActivityContext + ); + var parentContextParameterName = useParentContext ? "parentContext" : "parentId"; + + if (createOnly && startTimeParam != null) + { + output.Context.Diagnostic("StartTime parameter not allowed on Activity create method."); + + return; + } + + var kind = methodTarget.ActivityAttribute?.Kind ?? PropertyLibrary.Activities.DefaultActivityKind; + + var parentContextOrIdParameterValue = parentContextOrId?.ParameterName ?? "default"; + if (useParentContext && parentContextOrId!.ParameterType.IsNullable) + { + // parentContextOrId is not going to be null at this point as + // we already checked the type. + // If it's nullable we need to use the null-coalescing operator... + // and we need to ensure its explicit or the call is ambiguous + // between ActivityContext and ParentId. + parentContextOrIdParameterValue += " ?? default"; + } + + EmitActivityCall( + assignmentWriter, + methodTarget, + createOnly, + useParentContext, + parentContextParameterName, + parentContextOrIdParameterValue, + tagsParam, + linksParam, + startTimeParam, + kind + ); + } ); context.CancellationToken.ThrowIfCancellationRequested(); @@ -110,22 +110,18 @@ out var _ using (writer.OpenBlockScope()) { - EmitTagsOrBaggageParameters(writer, activityVariableName, true, methodTarget, false, logger); - EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, logger); + EmitTagsOrBaggageParameters(writer, activityVariableName, true, methodTarget, false, output); + EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, output); } } context.CancellationToken.ThrowIfCancellationRequested(); - if (methodTarget.ReturnType.Identity.Equals(TypeLibrary.Activities.SystemDiagnostics.Activity)) + if (methodTarget.ReturnType.Similar(TypeLibrary.Activities.SystemDiagnostics.Activity)) { - writer - .NewLine() - .Write("return ") - .Write(activityVariableName) - .Write(!emitNullable || methodTarget.ReturnType.IsNullable ? null : "!") - .Write(";") - .NewLine(); + writer.WriteReturn(returnWriter => + returnWriter.Write(activityVariableName).Write(methodTarget.ReturnType.IsNullable ? null : "!") + ); } } @@ -192,10 +188,10 @@ int kind } } - writer.WriteLine(");"); + writer.WriteLine(")"); } - static void EmitHasListenersTest(CodeWriter writer, ActivityBasedGenerationTarget methodTarget, bool emitNullable) + static void EmitHasListenersTest(CodeWriter writer, ActivityBasedGenerationTarget methodTarget) { var returnsVoid = methodTarget.ReturnType.Identity.SpecialType == SpecialType.System_Void; writer.Write("if (!").Write(PropertyLibrary.Activities.ActivitySourceFieldName).WriteLine(".HasListeners())"); @@ -203,13 +199,7 @@ static void EmitHasListenersTest(CodeWriter writer, ActivityBasedGenerationTarge using (writer.OpenBlockScope()) { writer.WriteLine( - "return" - + ( - returnsVoid - ? null - : " null" + (!emitNullable || methodTarget.ReturnType.IsNullable ? null : "!") - ) - + ";" + "return" + (returnsVoid ? null : " null" + (methodTarget.ReturnType.IsNullable ? null : "!")) + ";" ); } diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ContextMethods.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ContextMethods.cs index 5bd5bd57..e11c1f95 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ContextMethods.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ContextMethods.cs @@ -7,11 +7,10 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class ActivitySourceTargetClassEmitter { static void EmitContextMethodBody( - CodeWriter writer, + ActivityOutputContext output, ActivityBasedGenerationTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable = true + CodeWriter writer, + SourceProductionContext context ) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -19,8 +18,7 @@ static void EmitContextMethodBody( if ( !GuardParameters( methodTarget, - context, - logger, + output, out var activityParam, out var _, out var tagsParam, @@ -36,30 +34,30 @@ out var _ } var activityVariableName = - activityParam?.ParameterName ?? (TypeLibrary.Activities.SystemDiagnostics.Activity + ".Current"); + activityParam?.ParameterName ?? TypeLibrary.Activities.SystemDiagnostics.Activity.StaticMember("Current"); if (tagsParam != null) { - logger?.Diagnostic("Tags parameter not allowed on context method, only activities or events."); + output.Context.Diagnostic("Tags parameter not allowed on context method, only activities or events."); return; } if (linksParam != null) { - logger?.Diagnostic("Links parameter not allowed on context method, only activities."); + output.Context.Diagnostic("Links parameter not allowed on context method, only activities."); return; } - EmitHasListenersTest(writer, methodTarget, emitNullable); + EmitHasListenersTest(writer, methodTarget); writer.Write("if (").Write(activityVariableName).WriteLine(" != null)"); using (writer.OpenBlockScope()) { - EmitTagsOrBaggageParameters(writer, activityVariableName, true, methodTarget, false, logger); - EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, logger); + EmitTagsOrBaggageParameters(writer, activityVariableName, true, methodTarget, false, output); + EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, output); } context.CancellationToken.ThrowIfCancellationRequested(); diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.EventMethods.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.EventMethods.cs index fbdd4bdf..a6e33165 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.EventMethods.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.EventMethods.cs @@ -7,11 +7,10 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class ActivitySourceTargetClassEmitter { static void EmitEventMethodBody( - CodeWriter writer, + ActivityOutputContext output, ActivityBasedGenerationTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable = true + CodeWriter writer, + SourceProductionContext context ) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -19,8 +18,7 @@ static void EmitEventMethodBody( if ( !GuardParameters( methodTarget, - context, - logger, + output, out var activityParam, out var parentContextOrId, out var tagsParam, @@ -39,26 +37,26 @@ out var statusDescriptionParam activityParam?.ParameterName ?? (TypeLibrary.Activities.SystemDiagnostics.Activity + ".Current"); if (parentContextOrId != null) { - logger?.Diagnostic("Parent context/ Id not allowed on event method, only activities."); + output.Context.Diagnostic("Parent context/ Id not allowed on event method, only activities."); return; } if (linksParam != null) { - logger?.Diagnostic("Links parameter not allowed on event method, only activities."); + output.Context.Diagnostic("Links parameter not allowed on event method, only activities."); return; } if (startTimeParam != null) { - logger?.Diagnostic("Start time parameter not allowed on event method, only activities."); + output.Context.Diagnostic("Start time parameter not allowed on event method, only activities."); return; } - EmitHasListenersTest(writer, methodTarget, emitNullable); + EmitHasListenersTest(writer, methodTarget); writer.Write("if (").Write(activityVariableName).WriteLine(" != null)"); @@ -67,14 +65,7 @@ out var statusDescriptionParam var exceptionParam = methodTarget.Parameters.FirstOrDefault(m => m.IsException) ?? methodTarget.Tags.FirstOrDefault(m => m.IsException); - var tagsParameterName = EmitEventTags( - writer, - methodTarget, - activityVariableName, - tagsParam, - escapeParam, - emitNullable - ); + var tagsParameterName = EmitEventTags(writer, methodTarget, activityVariableName, tagsParam, escapeParam); var eventVariableName = "activityEvent" + methodTarget.MethodName; @@ -102,7 +93,7 @@ out var statusDescriptionParam { writer.NewLine(); - EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, logger); + EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, output); } EmitSetStatus(writer, methodTarget, activityVariableName, statusDescriptionParam, exceptionParam); @@ -121,8 +112,7 @@ static string EmitEventTags( ActivityBasedGenerationTarget methodTarget, string activityVariableName, ActivityBasedParameterTarget? tagsParam, - ActivityBasedParameterTarget? escapeParam, - bool emitNullable + ActivityBasedParameterTarget? escapeParam ) { var tagsParameterName = tagsParam?.ParameterName ?? "default"; @@ -135,7 +125,7 @@ bool emitNullable .Write(' ') .Write(tagsListVariableName) .Write( - emitNullable ? " = new(" : $" = new {TypeLibrary.Activities.SystemDiagnostics.ActivityTagsCollection}(" + " = new(" // : $" = new {TypeLibrary.Activities.SystemDiagnostics.ActivityTagsCollection}(" ); if (tagsParam != null) diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Fields.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Fields.cs index 81dca805..e0b97dc8 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Fields.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Fields.cs @@ -6,19 +6,15 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class ActivitySourceTargetClassEmitter { - static void EmitFields( - ActivitySourceTarget target, - CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? logger - ) + static void EmitFields(ActivityOutputContext output, CodeWriter writer, SourceProductionContext context) { context.CancellationToken.ThrowIfCancellationRequested(); + var target = output.Target; var activitySourceName = target.ActivitySourceName; if (string.IsNullOrWhiteSpace(activitySourceName)) { - logger?.Diagnostic("No activity source specified."); + output.Context.Diagnostic("No activity source specified."); activitySourceName = PropertyLibrary.Activities.DefaultActivitySourceName; } diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Methods.cs index bb34cb57..45134e33 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Methods.cs @@ -6,15 +6,11 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class ActivitySourceTargetClassEmitter { - static void EmitMethods( - ActivitySourceTarget target, - CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable - ) + static void EmitMethods(ActivityOutputContext output, CodeWriter writer, SourceProductionContext context) { - EmitRecordExceptionEvent(writer, context, logger, emitNullable); + var target = output.Target; + + EmitRecordExceptionEvent(output, writer, context); // Filter to only methods that are valid for Activities target // (have explicit Activity/Event/Context attributes, or valid inference in single-target) @@ -25,7 +21,9 @@ bool emitNullable { if (validActivityMethods.Any(m => m.MethodType != ActivityMethodType.Activity)) { - logger?.Diagnostic("There are no Activity methods defined, however there are Events/ Context methods."); + output.Context.Diagnostic( + "There are no Activity methods defined, however there are Events/ Context methods." + ); } } @@ -33,30 +31,29 @@ bool emitNullable { context.CancellationToken.ThrowIfCancellationRequested(); - EmitMethod(writer, methodTarget, target, context, logger, emitNullable); + EmitMethod(output, methodTarget, writer, context); } } static void EmitRecordExceptionEvent( + ActivityOutputContext output, CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable + SourceProductionContext context ) { context.CancellationToken.ThrowIfCancellationRequested(); - logger?.Debug($"Generating {PropertyLibrary.Activities.RecordExceptionMethodName}."); + output.Context.Debug($"Generating {PropertyLibrary.Activities.RecordExceptionMethodName}."); writer .Write("static void ") .Write(PropertyLibrary.Activities.RecordExceptionMethodName) .Write('(') - .Write(TypeLibrary.Activities.SystemDiagnostics.Activity) - .Write(emitNullable ? "? activity, " : " activity, ") - .Write(TypeLibrary.System.Exception) - .Write(emitNullable ? "? exception, " : " exception, ") - .Write(PropertyLibrary.BuiltInTypes.BoolKeyword) + .Write(TypeLibrary.Activities.SystemDiagnostics.Activity.MakeNullable(writer)) + .Write(" activity, ") + .Write(TypeLibrary.System.Exception.MakeNullable(writer)) + .Write(" exception, ") + .Write(PurviewTypeLibrary.System.Boolean) .WriteLine(" escape)"); using (writer.OpenBlockScope()) @@ -177,14 +174,14 @@ static void EmitThrowStub(CodeWriter writer, ActivityBasedGenerationTarget metho } static void EmitMethod( - CodeWriter writer, + ActivityOutputContext output, ActivityBasedGenerationTarget methodTarget, - ActivitySourceTarget target, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable + CodeWriter writer, + SourceProductionContext context ) { + var target = output.Target; + context.CancellationToken.ThrowIfCancellationRequested(); if (!methodTarget.TargetGenerationState.IsValid) @@ -202,7 +199,7 @@ bool emitNullable return; } - if (!GuardMethod(methodTarget, target, logger)) + if (!GuardMethod(methodTarget, output)) return; var isMultiTarget = methodTarget.TargetGenerationState.IsMultiTarget; @@ -213,25 +210,23 @@ bool emitNullable if (isMultiTarget) { // Generate private activity implementation method - EmitPrivateActivityMethod(writer, methodTarget, target, context, logger, emitNullable); + EmitPrivateActivityMethod(output, methodTarget, writer, context); // Generate public delegating method (Activity emitter owns this for multi-target) - EmitPublicDelegatingMethod(writer, methodTarget, methodTargets, context, logger, emitNullable); + EmitPublicDelegatingMethod(output, methodTarget, methodTargets, writer, context); } else { // Single-target: generate public method as before - EmitPublicActivityMethod(writer, methodTarget, context, logger, emitNullable); + EmitPublicActivityMethod(output, methodTarget, writer, context); } } static void EmitPrivateActivityMethod( - CodeWriter writer, + ActivityOutputContext output, ActivityBasedGenerationTarget methodTarget, - ActivitySourceTarget _, // target - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable + CodeWriter writer, + SourceProductionContext context ) { var privateMethodName = methodTarget.MethodName + "_Activity"; @@ -258,26 +253,25 @@ bool emitNullable ) { if (methodTarget.MethodType == ActivityMethodType.Activity) - EmitActivityMethodBody(writer, methodTarget, context, logger, emitNullable); + EmitActivityMethodBody(output, methodTarget, writer, context); else if (methodTarget.MethodType == ActivityMethodType.Event) - EmitEventMethodBody(writer, methodTarget, context, logger, emitNullable); + EmitEventMethodBody(output, methodTarget, writer, context); else if (methodTarget.MethodType == ActivityMethodType.Context) - EmitContextMethodBody(writer, methodTarget, context, logger, emitNullable); + EmitContextMethodBody(output, methodTarget, writer, context); } writer.NewLine(); } static void EmitPublicDelegatingMethod( - CodeWriter writer, + ActivityOutputContext output, ActivityBasedGenerationTarget methodTarget, GenerationType methodTargets, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable + CodeWriter writer, + SourceProductionContext context ) { - logger?.Debug($"Building public delegating method for {methodTarget.MethodName}."); + output.Context.Debug($"Building public delegating method for {methodTarget.MethodName}."); context.CancellationToken.ThrowIfCancellationRequested(); writer.NewLine(); @@ -356,11 +350,7 @@ bool emitNullable { writer .NewLine() - .Write( - "return activityResult" - + (!emitNullable || methodTarget.ReturnType.IsNullable ? null : "!") - + ";" - ); + .Write("return activityResult" + (methodTarget.ReturnType.IsNullable ? null : "!") + ";"); } } @@ -368,20 +358,15 @@ bool emitNullable } static void EmitPublicActivityMethod( - CodeWriter writer, + ActivityOutputContext output, ActivityBasedGenerationTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable + CodeWriter writer, + SourceProductionContext context ) { using ( writer.WriteMethodScope( - new MethodDeclarationOptions( - methodTarget.MethodName, - methodTarget.ReturnType, - TypeDeclarationAccessibility.Public - ) + new(methodTarget.MethodName, methodTarget.ReturnType, TypeDeclarationAccessibility.Public) { Parameters = [ @@ -396,33 +381,31 @@ bool emitNullable ) { if (methodTarget.MethodType == ActivityMethodType.Activity) - EmitActivityMethodBody(writer, methodTarget, context, logger, emitNullable); + EmitActivityMethodBody(output, methodTarget, writer, context); else if (methodTarget.MethodType == ActivityMethodType.Event) - EmitEventMethodBody(writer, methodTarget, context, logger, emitNullable); + EmitEventMethodBody(output, methodTarget, writer, context); else if (methodTarget.MethodType == ActivityMethodType.Context) - EmitContextMethodBody(writer, methodTarget, context, logger, emitNullable); + EmitContextMethodBody(output, methodTarget, writer, context); } writer.NewLine(); } - static bool GuardMethod( - ActivityBasedGenerationTarget methodTarget, - ActivitySourceTarget target, - ISourceGenLogger? logger - ) + static bool GuardMethod(ActivityBasedGenerationTarget methodTarget, ActivityOutputContext output) { + var target = output.Target; + if (!methodTarget.TargetGenerationState.IsValid) { if (methodTarget.TargetGenerationState.RaiseMultiGenerationTargetsNotSupported) { - logger?.Debug( + output.Context.Debug( $"Identified {target.InterfaceType.Identity.Name}.{methodTarget.MethodName} as problematic as it has another target types." ); } else if (methodTarget.TargetGenerationState.RaiseInferenceNotSupportedWithMultiTargeting) { - logger?.Debug( + output.Context.Debug( $"Identified {target.InterfaceType.Identity.Name}.{methodTarget.MethodName} as problematic as it is inferred." ); } @@ -441,7 +424,7 @@ static bool GuardMethod( if (!isValidReturnType) { - logger?.Diagnostic( + output.Context.Diagnostic( $"The return type {methodTarget.ReturnType} isn't valid for an activity, event, or context method." ); @@ -455,18 +438,18 @@ static bool GuardMethod( { if (!methodTarget.ReturnType.Identity.Equals(TypeLibrary.Activities.SystemDiagnostics.Activity)) { - logger?.Diagnostic($"No Activity returned for {methodTarget.MethodName}."); + output.Context.Diagnostic($"No Activity returned for {methodTarget.MethodName}."); } else if (!methodTarget.ReturnType.IsNullable) { - logger?.Diagnostic($"Activity return type is not nullable for {methodTarget.MethodName}."); + output.Context.Diagnostic($"Activity return type is not nullable for {methodTarget.MethodName}."); } } else { if (!methodTarget.HasActivityParameter) { - logger?.Diagnostic($"No Activity parameter is defined on {methodTarget.MethodName}."); + output.Context.Diagnostic($"No Activity parameter is defined on {methodTarget.MethodName}."); } else if ( !methodTarget @@ -474,7 +457,7 @@ static bool GuardMethod( .ParameterType.Identity.Equals(TypeLibrary.Activities.SystemDiagnostics.Activity) ) { - logger?.Diagnostic( + output.Context.Diagnostic( $"Activity parameter is defined, but it's not the first on {methodTarget.MethodName}." ); } diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Parameters.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Parameters.cs index bb4f0013..d727b80b 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Parameters.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Parameters.cs @@ -13,7 +13,7 @@ static void EmitTagsOrBaggageParameters( bool populateTags, ActivityBasedGenerationTarget method, bool checkForNullableActivity, - ISourceGenLogger? logger + ActivityOutputContext output ) { var parameters = populateTags ? method.Tags : method.Baggage; @@ -36,7 +36,7 @@ void EmitParameter(ActivityBasedParameterTarget param) if (!populateTags && param.ParameterType.Identity.SpecialType != SpecialType.System_String) { - logger?.Diagnostic("Found a baggage parameter type that is not a string."); + output.Context.Diagnostic("Found a baggage parameter type that is not a string."); if (param.ParameterType.IsNullable) writer.Write('?'); @@ -81,8 +81,7 @@ void EmitParameters() static bool GuardParameters( ActivityBasedGenerationTarget methodTarget, - SourceProductionContext _, - ISourceGenLogger? logger, + ActivityOutputContext output, out ActivityBasedParameterTarget? activityParam, out ActivityBasedParameterTarget? parentContextOrId, out ActivityBasedParameterTarget? tagsParam, @@ -129,7 +128,7 @@ out ActivityBasedParameterTarget? statusDescriptionParam if (activityParams.Length > 1) { - logger?.Diagnostic("More than one activity parameter defined."); + output.Context.Diagnostic("More than one activity parameter defined."); return false; } @@ -140,7 +139,7 @@ out ActivityBasedParameterTarget? statusDescriptionParam if (parentContextOrIdParams.Length > 1) { - logger?.Diagnostic("More than one parent context/ id defined."); + output.Context.Diagnostic("More than one parent context/ id defined."); return false; } @@ -151,7 +150,7 @@ out ActivityBasedParameterTarget? statusDescriptionParam if (tagsParams.Length > 1) { - logger?.Diagnostic("More than one tag IEnumerable defined."); + output.Context.Diagnostic("More than one tag IEnumerable defined."); return false; } @@ -162,7 +161,7 @@ out ActivityBasedParameterTarget? statusDescriptionParam if (linksParams.Length > 1) { - logger?.Diagnostic("More than one ActivityLink/ IEnumerable of ActivityLink is defined."); + output.Context.Diagnostic("More than one ActivityLink/ IEnumerable of ActivityLink is defined."); return false; } @@ -173,7 +172,7 @@ out ActivityBasedParameterTarget? statusDescriptionParam if (escapeParams.Length > 1) { - logger?.Diagnostic("More than one Escape parameter defined."); + output.Context.Diagnostic("More than one Escape parameter defined."); return false; } @@ -196,7 +195,7 @@ out ActivityBasedParameterTarget? statusDescriptionParam if (statusDescriptionParams.Length > 1) { - logger?.Diagnostic("More than one StatusDescription parameter defined."); + output.Context.Diagnostic("More than one StatusDescription parameter defined."); return false; } diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.cs index a688cd7c..7d5ebdab 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.cs @@ -8,22 +8,22 @@ static partial class ActivitySourceTargetClassEmitter public static void GenerateImplementation(ActivityOutputContext output, SourceProductionContext context) { var target = output.Target; - var logger = output.Context.Logger; - var emitNullable = output.Context.Capabilities.SupportsNullableAnnotations; + //var emitNullable = output.Context.Capabilities.SupportsNullableAnnotations; - logger?.Debug($"Generating activity class for: {target.FullyQualifiedName}"); - - var writer = output.Context.CreateCodeWriter(); - EmitterHelpers.WriteAutoGeneratedHeader(writer, emitNullable); + output.Context.Debug($"Generating activity class for: {target.FullyQualifiedName}"); + var writer = output.CreateWriter(); using (writer.WriteBlockNamespaceScope(target.ClassNamespace)) { List parentScopes = []; - foreach (var parent in target.ParentClasses) + if (target.TelemetryGeneration.TelemetryNamesNamespace == null) { - parentScopes.Add( - writer.WriteClassScope(new(parent) { IsSealed = false, IncludeGeneratedAttributes = false }) - ); + foreach (var parent in target.ParentClasses) + { + parentScopes.Add( + writer.WriteClassScope(new(parent) { IsSealed = false, IncludeGeneratedAttributes = false }) + ); + } } using ( @@ -37,8 +37,8 @@ public static void GenerateImplementation(ActivityOutputContext output, SourcePr ) ) { - EmitFields(target, writer, context, logger); - EmitMethods(target, writer, context, logger, emitNullable); + EmitFields(output, writer, context); + EmitMethods(output, writer, context); } foreach (var scope in parentScopes) @@ -48,12 +48,12 @@ public static void GenerateImplementation(ActivityOutputContext output, SourcePr context.AddSource($"{target.FullyQualifiedName}.Activity.g.cs", writer); DependencyInjectionClassEmitter.GenerateImplementation( + output.CreateWriter(), GenerationType.Activities, target.TelemetryGeneration, target.GenerationType, target.ClassNameToGenerate, - target.InterfaceType.Identity.Name, - target.FullNamespace, + target.InterfaceType, context, output.Context ); diff --git a/src/src/SourceGenerator/Emitters/ConstructorEmitter.cs b/src/src/SourceGenerator/Emitters/ConstructorEmitter.cs index fdaac2ee..847b77e5 100644 --- a/src/src/SourceGenerator/Emitters/ConstructorEmitter.cs +++ b/src/src/SourceGenerator/Emitters/ConstructorEmitter.cs @@ -13,11 +13,10 @@ public static void EmitCtor( GenerationType requestingType, GenerationType generationType, string classNameToGenerate, - string fullyQualifiedInterfaceName, + TypeReference interfaceType, CodeWriter writer, SourceProductionContext context, - ISourceGenLogger? logger, - bool supportsIMeterFactory = true + GenerationContext generationContext ) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -25,7 +24,7 @@ public static void EmitCtor( // Only emit constructor from one target to avoid duplicate definitions if (!SharedHelpers.ShouldEmitConstructor(requestingType, generationType)) { - logger?.Debug($"Skipping constructor emit for {requestingType} ({generationType})."); + generationContext.Debug($"Skipping constructor emit for {requestingType} ({generationType})."); return; } @@ -34,30 +33,28 @@ public static void EmitCtor( writer.WriteConstructor( new ConstructorDeclarationOptions(classNameToGenerate, TypeDeclarationAccessibility.Public) { - Parameters = BuildParameters(generationType, fullyQualifiedInterfaceName, supportsIMeterFactory), + Parameters = BuildParameters(generationType, interfaceType, generationContext), IncludeGeneratedAttributes = false, }, - body => EmitBody(generationType, body, supportsIMeterFactory) + body => EmitBody(generationType, body, generationContext) ); } static ImmutableArray BuildParameters( GenerationType generationType, - string? loggerFullyQualifiedInterfaceName, - bool supportsIMeterFactory + TypeReference interfaceType, + GenerationContext generationContext ) { var builder = ImmutableArray.CreateBuilder(); if (generationType.HasFlag(GenerationType.Logging)) { - var loggerType = TypeLibrary.Logging.MicrosoftExtensions.ILogger.MakeGeneric( - new TypeReference(new TypeIdentity(loggerFullyQualifiedInterfaceName!, null)) - ); + var loggerType = TypeLibrary.Logging.MicrosoftExtensions.ILogger.MakeGeneric(interfaceType); builder.Add(new ParameterDeclarationOptions(LoggerParameterName, new TypeReference(loggerType))); } - if (generationType.HasFlag(GenerationType.Metrics) && supportsIMeterFactory) + if (generationType.HasFlag(GenerationType.Metrics) && generationContext.Capabilities.SupportsIMeterFactory) { builder.Add( new ParameterDeclarationOptions( @@ -70,7 +67,11 @@ bool supportsIMeterFactory return builder.ToImmutable(); } - static void EmitBody(GenerationType generationType, CodeWriter writer, bool supportsIMeterFactory) + static void EmitBody( + GenerationType generationType, + CodeWriter writer, + GenerationContext generationContext + ) { if (generationType.HasFlag(GenerationType.Logging)) { @@ -86,7 +87,7 @@ static void EmitBody(GenerationType generationType, CodeWriter writer, bool supp { writer.Write(PropertyLibrary.Metrics.MeterInitializationMethod).Write('('); - if (supportsIMeterFactory) + if (generationContext.Capabilities.SupportsIMeterFactory) writer.Write(PropertyLibrary.Metrics.MeterFactoryParameterName); writer.Write(");").NewLine(); diff --git a/src/src/SourceGenerator/Emitters/DependencyInjectionClassEmitter.cs b/src/src/SourceGenerator/Emitters/DependencyInjectionClassEmitter.cs index cc73fa67..1f92fa7a 100644 --- a/src/src/SourceGenerator/Emitters/DependencyInjectionClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/DependencyInjectionClassEmitter.cs @@ -1,3 +1,4 @@ +using System.Text; using Microsoft.CodeAnalysis; using Purview.Telemetry.SourceGenerator.Helpers; using Purview.Telemetry.SourceGenerator.Records; @@ -7,30 +8,27 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; static class DependencyInjectionClassEmitter { public static void GenerateImplementation( + CodeWriter writer, GenerationType requestingType, TelemetryGenerationAttributeData attribute, GenerationType generationType, string implementationClassName, - string sourceInterfaceName, - string? fullyQualifiedNamespace, + TypeReference interfaceType, SourceProductionContext context, GenerationContext generationContext ) { context.CancellationToken.ThrowIfCancellationRequested(); - var logger = generationContext.Logger; - var emitNullable = generationContext.Capabilities.SupportsNullableAnnotations; - if (!attribute.GenerateDependencyExtension) { - logger?.Debug("Skipping dependency injection emit."); + generationContext.Debug("Skipping dependency injection emit."); return; } if (!SharedHelpers.ShouldEmitDIExtension(requestingType, generationType)) { - logger?.Debug($"Skipping dependency injection emit for {requestingType} ({generationType})."); + generationContext.Debug($"Skipping dependency injection emit for {requestingType} ({generationType})."); return; } @@ -42,16 +40,22 @@ GenerationContext generationContext ? TypeDeclarationAccessibility.Public : TypeDeclarationAccessibility.Internal; - logger?.Debug( - $"Generating service dependency class {classNameToGenerate} for: {fullyQualifiedNamespace}{sourceInterfaceName}" + generationContext.Debug( + $"Generating service dependency class {classNameToGenerate} for: {interfaceType.RenderFullName}" ); context.CancellationToken.ThrowIfCancellationRequested(); - var writer = new CodeWriter(GenerationSettings.Create()); - EmitterHelpers.WriteAutoGeneratedHeader(writer, emitNullable); + // When the DI class is placed in a custom namespace (TelemetryNamesNamespace), the + // AddSingleton extension method is no longer in scope, so import it explicitly. + if (attribute.TelemetryNamesNamespace != null) + writer.WriteUsing(PropertyLibrary.DependencyInjection.DependencyInjectionNamespace); - using (writer.WriteBlockNamespaceScope(PropertyLibrary.DependencyInjection.DependencyInjectionNamespace)) + using ( + writer.WriteBlockNamespaceScope( + attribute.TelemetryNamesNamespace ?? PropertyLibrary.DependencyInjection.DependencyInjectionNamespace + ) + ) { using ( writer.WriteClassScope( @@ -67,35 +71,37 @@ GenerationContext generationContext EmitMethod( writer, implementationClassName, - sourceInterfaceName, - fullyQualifiedNamespace, - logger, + interfaceType, + attribute.TelemetryNamesNamespace, + generationContext, context.CancellationToken ); } } - var hintName = $"{fullyQualifiedNamespace}{classNameToGenerate}.DependencyInjection.g.cs"; + var hintName = + $"{BuildImplQualifiedName(attribute.TelemetryNamesNamespace, interfaceType, classNameToGenerate!)}.DependencyInjection.g.cs"; context.AddSource(hintName, writer); } static void EmitMethod( CodeWriter writer, string className, - string interfaceName, - string? fullyQualifiedNamespace, - ISourceGenLogger? logger, + TypeReference interfaceType, + string? telemetryNamesNamespace, + GenerationContext generationContext, CancellationToken token ) { token.ThrowIfCancellationRequested(); - logger?.Debug($"Emitting DI method for {interfaceName}."); - + var interfaceName = interfaceType.Identity.Name; var methodName = interfaceName; if (methodName[0] == 'I') methodName = methodName.Substring(1); + generationContext.Debug($"Emitting DI method for {interfaceName}."); + using ( writer.WriteMethodScope( new MethodDeclarationOptions( @@ -119,14 +125,37 @@ CancellationToken token { writer .Write("return services.AddSingleton<") - .Write("global::") - .Write(fullyQualifiedNamespace) - .Write(interfaceName) + .Write(interfaceType.RenderFullName) .Write(", ") .Write("global::") - .Write(fullyQualifiedNamespace) - .Write(className) + .Write(BuildImplQualifiedName(telemetryNamesNamespace, interfaceType, className)) .WriteLine(">();"); } } + + /// + /// Builds the fully-qualified (non global::-prefixed) name of the generated implementation + /// class from the interface identity's namespace and containing-type chain plus the class name. + /// When is supplied, the implementation class lives in + /// that namespace instead. + /// + static string BuildImplQualifiedName(string? telemetryNamesNamespace, TypeReference interfaceType, string className) + { + var identity = interfaceType.Identity; + + var builder = new StringBuilder(); + var ns = telemetryNamesNamespace ?? identity.Namespace; + if (ns != null) + builder.Append(ns).Append('.'); + + if (telemetryNamesNamespace == null) + { + foreach (var containingType in identity.ContainingTypes) + builder.Append(containingType.Name).Append('.'); + } + + builder.Append(className); + + return builder.ToString(); + } } diff --git a/src/src/SourceGenerator/Emitters/EmitterHelpers.cs b/src/src/SourceGenerator/Emitters/EmitterHelpers.cs index 09c122a6..0c9eb888 100644 --- a/src/src/SourceGenerator/Emitters/EmitterHelpers.cs +++ b/src/src/SourceGenerator/Emitters/EmitterHelpers.cs @@ -12,41 +12,18 @@ static class EmitterHelpers /// so they stay hidden from IntelliSense. /// public static AttributeDeclarationOptions EditorBrowsableAttribute() => - new(new TypeIdentity("EditorBrowsableAttribute", "System.ComponentModel")) + new(new TypeIdentity(nameof(EditorBrowsableAttribute), "System.ComponentModel")) { - Arguments = [new AttributeArgumentOptions("global::System.ComponentModel.EditorBrowsableState.Never")], + Arguments = [new("global::System.ComponentModel.EditorBrowsableState.Never")], }; - /// - /// Writes the deterministic auto-generated file header, conditionally enabling nullable - /// annotations. is false for C# 7.3 / .NET Framework targets. - /// - public static CodeWriter WriteAutoGeneratedHeader(CodeWriter writer, bool emitNullable) - { - writer - .Write("// ") - .NewLine() - .Write("// This code was generated by the Purview.Telemetry.SourceGenerator") - .NewLine() - .Write("// Changes to this file will be lost when the source generator runs again.") - .NewLine() - .NewLine(); - - if (emitNullable) - writer.WriteLine("#nullable enable"); - - return writer.WriteLine("#pragma warning disable 1591").NewLine(); - } - /// /// Adds a finished as a generated source file. The writer's /// disposable scopes must all be closed before calling this; materializing the source /// validates that they are. /// - public static void AddSourceFile(CodeWriter writer, string hintName, SourceProductionContext context) - { + public static void AddSourceFile(CodeWriter writer, string hintName, SourceProductionContext context) => context.AddSource(hintName, writer); - } /// /// Opens the generated implementation-class scope. No generated attributes are emitted; the diff --git a/src/src/SourceGenerator/Emitters/MarkerAttributeTemplateEmitter.cs b/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs similarity index 70% rename from src/src/SourceGenerator/Emitters/MarkerAttributeTemplateEmitter.cs rename to src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs index 2b5c3dcf..2c08136d 100644 --- a/src/src/SourceGenerator/Emitters/MarkerAttributeTemplateEmitter.cs +++ b/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs @@ -1,9 +1,6 @@ using System.Collections.Immutable; -using System.Text; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; using Purview.Telemetry.SourceGenerator.Helpers; -using Purview.Telemetry.SourceGenerator.Templates; namespace Purview.Telemetry.SourceGenerator.Emitters; @@ -12,98 +9,84 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; /// previously shipped as a static embedded resource; it is now built in memory with a /// inside RegisterPostInitializationOutput. /// -static class MarkerAttributeTemplateEmitter +static class GeneratedTypesEmitter { - static readonly Dictionary> Emitters = new(StringComparer.Ordinal) + static readonly Dictionary> Emitters = new() { - ["TagAttribute"] = WriteTagLikeAttribute, - ["BaggageAttribute"] = WriteTagLikeAttribute, - ["ExcludeAttribute"] = (writer, type) => + // Telemetry Shared + [TypeLibrary.TelemetryShared.TagAttribute] = WriteTagLikeAttribute, + [TypeLibrary.TelemetryShared.ExcludeAttribute] = (writer, type) => WriteSimpleAttribute(writer, type, AttributeTargets.Method, includeSuppressMessage: false), - ["TelemetryGenerationAttribute"] = WriteTelemetryGenerationAttribute, - ["Targets"] = WriteTargetsEnum, - ["NamingConvention"] = WriteNamingConventionEnum, - ["ExcludeTargetsAttribute"] = WriteExcludeTargetsAttribute, - ["ActivitySourceGenerationAttribute"] = WriteActivitySourceGenerationAttribute, - ["ActivitySourceAttribute"] = WriteActivitySourceAttribute, - ["ActivityAttribute"] = WriteActivityAttribute, - ["EventAttribute"] = WriteEventAttribute, - ["ContextAttribute"] = (writer, type) => + [TypeLibrary.TelemetryShared.TelemetryGenerationAttribute] = WriteTelemetryGenerationAttribute, + [TypeLibrary.TelemetryShared.TargetsEnum] = WriteTargetsEnum, + [TypeLibrary.TelemetryShared.NamingConventionEnum] = WriteNamingConventionEnum, + [TypeLibrary.TelemetryShared.ExcludeTargetsAttribute] = WriteExcludeTargetsAttribute, + // Activities + [TypeLibrary.Activities.BaggageAttribute] = WriteTagLikeAttribute, + [TypeLibrary.Activities.ActivitySourceGenerationAttribute] = WriteActivitySourceGenerationAttribute, + [TypeLibrary.Activities.ActivitySourceAttribute] = WriteActivitySourceAttribute, + [TypeLibrary.Activities.ActivityAttribute] = WriteActivityAttribute, + [TypeLibrary.Activities.EventAttribute] = WriteEventAttribute, + [TypeLibrary.Activities.ContextAttribute] = (writer, type) => WriteSimpleAttribute(writer, type, AttributeTargets.Method, includeSuppressMessage: false), - ["EscapeAttribute"] = (writer, type) => + [TypeLibrary.Activities.EscapeAttribute] = (writer, type) => WriteSimpleAttribute(writer, type, AttributeTargets.Parameter, includeSuppressMessage: false), - ["StatusDescriptionAttribute"] = (writer, type) => + [TypeLibrary.Activities.StatusDescriptionAttribute] = (writer, type) => WriteSimpleAttribute(writer, type, AttributeTargets.Parameter, includeSuppressMessage: false), - ["LoggerGenerationAttribute"] = WriteLoggerGenerationAttribute, - ["LoggerAttribute"] = WriteLoggerAttribute, - ["LogAttribute"] = WriteLogAttribute, - ["LogPrefixType"] = WriteLogPrefixTypeEnum, - ["LoggerGenerationMode"] = WriteLoggerGenerationModeEnum, - ["ExpandEnumerableAttribute"] = WriteExpandEnumerableAttribute, - ["TraceAttribute"] = WriteSpecificLogAttribute, - ["DebugAttribute"] = WriteSpecificLogAttribute, - ["InfoAttribute"] = WriteSpecificLogAttribute, - ["WarningAttribute"] = WriteSpecificLogAttribute, - ["ErrorAttribute"] = WriteSpecificLogAttribute, - ["CriticalAttribute"] = WriteSpecificLogAttribute, - ["MeterGenerationAttribute"] = WriteMeterGenerationAttribute, - ["MeterAttribute"] = WriteMeterAttribute, - ["MeterNameGenerationType"] = WriteMeterNameGenerationTypeEnum, - ["InstrumentMeasurementAttribute"] = (writer, type) => + // Logging + [TypeLibrary.Logging.LoggerGenerationAttribute] = WriteLoggerGenerationAttribute, + [TypeLibrary.Logging.LoggerAttribute] = WriteLoggerAttribute, + [TypeLibrary.Logging.LogAttribute] = WriteLogAttribute, + [TypeLibrary.Logging.LogPrefixType] = WriteLogPrefixTypeEnum, + [TypeLibrary.Logging.LoggerGenerationMode] = WriteLoggerGenerationModeEnum, + [TypeLibrary.Logging.ExpandEnumerableAttribute] = WriteExpandEnumerableAttribute, + [TypeLibrary.Logging.TraceAttribute] = WriteSpecificLogAttribute, + [TypeLibrary.Logging.DebugAttribute] = WriteSpecificLogAttribute, + [TypeLibrary.Logging.InfoAttribute] = WriteSpecificLogAttribute, + [TypeLibrary.Logging.WarningAttribute] = WriteSpecificLogAttribute, + [TypeLibrary.Logging.ErrorAttribute] = WriteSpecificLogAttribute, + [TypeLibrary.Logging.CriticalAttribute] = WriteSpecificLogAttribute, + // Metrics + [TypeLibrary.Metrics.MeterGenerationAttribute] = WriteMeterGenerationAttribute, + [TypeLibrary.Metrics.MeterAttribute] = WriteMeterAttribute, + [TypeLibrary.Metrics.MeterNameGenerationType] = WriteMeterNameGenerationTypeEnum, + [TypeLibrary.Metrics.InstrumentMeasurementAttribute] = (writer, type) => WriteSimpleAttribute(writer, type, AttributeTargets.Parameter, includeSuppressMessage: false), - ["AutoCounterAttribute"] = WriteAutoCounterAttribute, - ["CounterAttribute"] = WriteCounterLikeAttribute, - ["UpDownCounterAttribute"] = WriteCounterLikeAttribute, - ["HistogramAttribute"] = WriteCounterLikeAttribute, - ["ObservableCounterAttribute"] = WriteObservableCounterLikeAttribute, - ["ObservableUpDownCounterAttribute"] = WriteObservableCounterLikeAttribute, - ["ObservableGaugeAttribute"] = WriteObservableCounterLikeAttribute, + [TypeLibrary.Metrics.AutoCounterAttribute] = WriteAutoCounterAttribute, + [TypeLibrary.Metrics.CounterAttribute] = WriteCounterLikeAttribute, + [TypeLibrary.Metrics.UpDownCounterAttribute] = WriteCounterLikeAttribute, + [TypeLibrary.Metrics.HistogramAttribute] = WriteCounterLikeAttribute, + [TypeLibrary.Metrics.ObservableCounterAttribute] = WriteObservableCounterLikeAttribute, + [TypeLibrary.Metrics.ObservableUpDownCounterAttribute] = WriteObservableCounterLikeAttribute, + [TypeLibrary.Metrics.ObservableGaugeAttribute] = WriteObservableCounterLikeAttribute, }; public static void EmitAll(IncrementalGeneratorPostInitializationContext context) { - var settings = GenerationSettings.Create(); + // Adds Microsoft.CodeAnalysis.EmbeddedAttribute to the compilation so generated marker + // types (decorated with [Microsoft.CodeAnalysis.Embedded]) are invisible to downstream + // assemblies, preventing CS0436 conflicts when multiple projects reference this generator. + context.AddEmbeddedAttributeDefinition(); - foreach (var template in TemplateLibrary.GetAllTemplates()) + var settings = GenerationSettings.Create(); + foreach (var type in TypeLibrary.GetAllGeneratedTypes()) { - var writer = new CodeWriter(settings); - Emit(writer, template); - context.AddSource(template.GetGeneratedFilename(), SourceText.From(writer.ToString(), Encoding.UTF8)); - } - } + CodeWriter writer = new(settings); + writer.WriteAutoGeneratedHeader(); - static void Emit(CodeWriter writer, TemplateInfo template) - { - if (!Emitters.TryGetValue(template.Name, out var emit)) - throw new ArgumentOutOfRangeException( - nameof(template), - template.Name, - "Unknown marker-attribute template." - ); + Emit(writer, type); - emit(writer, template.TypeInfo); + context.AddSource($"{type.MetadataFullName}.g.cs", writer); + } } - // ------------------------------------------------------------------------------------------- - // File scaffolding - // ------------------------------------------------------------------------------------------- - - static void WriteHeader(CodeWriter writer) + static void Emit(CodeWriter writer, TypeIdentity type) { - writer - .WriteLine("// ") - .WriteLine("// This code was generated by the Purview.Telemetry.SourceGenerator.") - .WriteLine("// Changes to this file will be lost when the source generator runs again.") - .NewLine() - .WriteLine("#if !NET48_OR_GREATER && !PURVIEW_TELEMETRY_NON_NULLABLE") - .WriteLine("#nullable enable") - .WriteLine("#endif") - .NewLine() - .WriteLine("#pragma warning disable 1591") - .NewLine(); - } + if (!Emitters.TryGetValue(type, out var emit)) + throw new ArgumentOutOfRangeException(nameof(type), type.Name, "Unknown generation type requested."); - static void WriteFileScopedNamespace(CodeWriter writer) => writer.WriteFileScopedNamespace("Purview.Telemetry"); + emit(writer, type); + } // ------------------------------------------------------------------------------------------- // Attribute templates @@ -119,36 +102,24 @@ static void EmitAttribute( bool includeSuppressMessage = true ) { - WriteHeader(writer); - if (wrapInExcludeLoggingGuard) writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); - WriteFileScopedNamespace(writer); - WriteAttributeTemplate(writer, type, targets, body, includeSuppressMessage); - - if (wrapInExcludeLoggingGuard) - writer.WriteLine("#endif"); - } - - static void WriteAttributeTemplate( - CodeWriter writer, - TypeIdentity type, - AttributeTargets targets, - Action body, - bool includeSuppressMessage - ) - { var attributes = ImmutableArray.Empty; attributes = attributes.Add(ConditionalAttribute()); if (includeSuppressMessage) attributes = attributes.Add(SuppressMessageAttribute()); - writer.WriteAttributeClass( - new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Internal) { Attributes = attributes }, - targets, - body - ); + writer + .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .WriteAttributeClass( + new(type.Name, TypeDeclarationAccessibility.Internal) { Attributes = attributes }, + targets, + body + ); + + if (wrapInExcludeLoggingGuard) + writer.WriteLine("#endif"); } static void WriteSimpleAttribute( @@ -164,17 +135,13 @@ bool includeSuppressMessage static AttributeDeclarationOptions ConditionalAttribute() => new(new TypeIdentity("ConditionalAttribute", "System.Diagnostics")) { - Arguments = [new AttributeArgumentOptions("\"PURVIEW_TELEMETRY_ATTRIBUTES\"")], + Arguments = [new("\"PURVIEW_TELEMETRY_ATTRIBUTES\"")], }; static AttributeDeclarationOptions SuppressMessageAttribute() => new(new TypeIdentity("SuppressMessageAttribute", "System.Diagnostics.CodeAnalysis")) { - Arguments = - [ - new AttributeArgumentOptions("\"Design\""), - new AttributeArgumentOptions("\"CA1019:Define accessors for attribute arguments\""), - ], + Arguments = [new("\"Design\""), new("\"CA1019:Define accessors for attribute arguments\"")], }; // ------------------------------------------------------------------------------------------- @@ -191,7 +158,10 @@ static void WriteNameConstructor(CodeWriter writer, TypeIdentity type) => writer.WriteConstructor( new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = [new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference())], + Parameters = + [ + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), + ], }, ctor => ctor.WriteAssignment("Name", "name") ); @@ -202,7 +172,10 @@ static void WriteMessageTemplateConstructor(CodeWriter writer, TypeIdentity type { Parameters = [ - new ParameterDeclarationOptions("messageTemplate", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions( + "messageTemplate", + PurviewTypeLibrary.System.String.AsTypeReference() + ), ], }, ctor => ctor.WriteAssignment("MessageTemplate", "messageTemplate") @@ -212,7 +185,7 @@ static void WriteEventIdConstructor(CodeWriter writer, TypeIdentity type) => writer.WriteConstructor( new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = [new ParameterDeclarationOptions("eventId", TypeLibrary.System.Int32.AsTypeReference())], + Parameters = [new("eventId", PurviewTypeLibrary.System.Int32.AsTypeReference())], }, ctor => ctor.WriteAssignment("EventId", "eventId") ); @@ -239,7 +212,7 @@ static void WriteNullableStringProperty(CodeWriter writer, string name) writer.WriteProperty( new PropertyDeclarationOptions( name, - TypeLibrary.System.String.AsTypeReference(), + PurviewTypeLibrary.System.String.AsTypeReference(), TypeDeclarationAccessibility.Public ) { @@ -249,11 +222,7 @@ static void WriteNullableStringProperty(CodeWriter writer, string name) ); writer.WriteLine("#else"); writer.WriteProperty( - new PropertyDeclarationOptions( - name, - TypeLibrary.System.String.AsTypeReference().Nullable(), - TypeDeclarationAccessibility.Public - ) + new(name, PurviewTypeLibrary.System.String.MakeNullable(writer), TypeDeclarationAccessibility.Public) { HasSetter = true, IncludeGeneratedAttributes = false, @@ -268,7 +237,7 @@ static void WritePlainStringProperty(CodeWriter writer, string name, string? ini writer.WriteProperty( new PropertyDeclarationOptions( name, - TypeLibrary.System.String.AsTypeReference(), + PurviewTypeLibrary.System.String.AsTypeReference(), TypeDeclarationAccessibility.Public ) { @@ -299,7 +268,7 @@ static void WriteTagLikeAttribute(CodeWriter writer, TypeIdentity type) [ new ParameterDeclarationOptions( "skipOnNullOrEmpty", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ), ], }, @@ -310,10 +279,10 @@ static void WriteTagLikeAttribute(CodeWriter writer, TypeIdentity type) { Parameters = [ - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), new ParameterDeclarationOptions( "skipOnNullOrEmpty", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ) { DefaultValue = "false", @@ -328,14 +297,14 @@ static void WriteTagLikeAttribute(CodeWriter writer, TypeIdentity type) ); WriteNullableStringProperty(body, "Name"); - WritePublicProperty(body, "SkipOnNullOrEmpty", TypeLibrary.System.Boolean.AsTypeReference()); + WritePublicProperty(body, "SkipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean.AsTypeReference()); } ); } static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity type) { - var namingConvention = TypeLibrary.Telemetry.NamingConventionEnum; + var namingConvention = TypeLibrary.TelemetryShared.NamingConventionEnum; EmitAttribute( writer, @@ -351,15 +320,18 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty [ new ParameterDeclarationOptions( "generateDependencyExtension", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ), - new ParameterDeclarationOptions("className", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions( + "className", + PurviewTypeLibrary.System.String.AsTypeReference() + ) { DefaultValue = "null", }, new ParameterDeclarationOptions( "dependencyInjectionClassName", - TypeLibrary.System.String.AsTypeReference() + PurviewTypeLibrary.System.String.AsTypeReference() ) { DefaultValue = "null", @@ -378,10 +350,13 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty { Parameters = [ - new ParameterDeclarationOptions("className", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions( + "className", + PurviewTypeLibrary.System.String.AsTypeReference() + ), new ParameterDeclarationOptions( "dependencyInjectionClassName", - TypeLibrary.System.String.AsTypeReference() + PurviewTypeLibrary.System.String.AsTypeReference() ) { DefaultValue = "null", @@ -398,7 +373,7 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty WritePublicProperty( body, "GenerateDependencyExtension", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); WriteNullableStringProperty(body, "ClassName"); @@ -406,7 +381,7 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty WritePublicProperty( body, "DependencyInjectionClassIsPublic", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ); WritePublicProperty( body, @@ -417,17 +392,18 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty WritePublicProperty( body, "GenerateTelemetryNamesClass", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); WriteNullableStringProperty(body, "TelemetryNamesClassName"); + WriteNullableStringProperty(body, "TelemetryNamesNamespace"); } ); } static void WriteExcludeTargetsAttribute(CodeWriter writer, TypeIdentity type) { - var targets = TypeLibrary.Telemetry.TargetsEnum; + var targets = TypeLibrary.TelemetryShared.TargetsEnum; EmitAttribute( writer, @@ -467,17 +443,17 @@ static void WriteActivitySourceGenerationAttribute(CodeWriter writer, TypeIdenti { Parameters = [ - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), new ParameterDeclarationOptions( "defaultToTags", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ) { DefaultValue = "true", }, new ParameterDeclarationOptions( "generateDiagnosticsForMissingActivity", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ) { DefaultValue = "true", @@ -499,19 +475,19 @@ static void WriteActivitySourceGenerationAttribute(CodeWriter writer, TypeIdenti ); WriteNullableStringProperty(body, "Name"); - WritePublicProperty(body, "DefaultToTags", TypeLibrary.System.Boolean.AsTypeReference(), "true"); + WritePublicProperty(body, "DefaultToTags", PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true"); WriteNullableStringProperty(body, "BaggageAndTagPrefix"); WritePlainStringProperty(body, "BaggageAndTagSeparator", "\".\""); WritePublicProperty( body, "LowercaseBaggageAndTagKeys", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); WritePublicProperty( body, "GenerateDiagnosticsForMissingActivity", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); } @@ -535,20 +511,20 @@ static void WriteActivitySourceAttribute(CodeWriter writer, TypeIdentity type) WriteNullableStringProperty(body, "Name"); body.XmlSummary("Specifies the default when inferring between tag or baggage."); - WritePublicProperty(body, "DefaultToTags", TypeLibrary.System.Boolean.AsTypeReference(), "true"); + WritePublicProperty(body, "DefaultToTags", PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true"); WriteNullableStringProperty(body, "BaggageAndTagPrefix"); body.XmlSummary("Determines if the name is used as a prefix."); WritePublicProperty( body, "IncludeActivitySourcePrefix", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); body.XmlSummary("Determines if tag/ baggage names are lowercased."); WritePublicProperty( body, "LowercaseBaggageAndTagKeys", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); } @@ -579,12 +555,15 @@ static void WriteActivityAttribute(CodeWriter writer, TypeIdentity type) { Parameters = [ - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), new ParameterDeclarationOptions("kind", activityKind.AsTypeReference()) { DefaultValue = $"{activityKind.RenderFullName}.Internal", }, - new ParameterDeclarationOptions("createOnly", TypeLibrary.System.Boolean.AsTypeReference()) + new ParameterDeclarationOptions( + "createOnly", + PurviewTypeLibrary.System.Boolean.AsTypeReference() + ) { DefaultValue = "false", }, @@ -600,7 +579,7 @@ static void WriteActivityAttribute(CodeWriter writer, TypeIdentity type) WriteNullableStringProperty(body, "Name"); WritePublicProperty(body, "Kind", activityKind.AsTypeReference()); - WritePublicProperty(body, "CreateOnly", TypeLibrary.System.Boolean.AsTypeReference()); + WritePublicProperty(body, "CreateOnly", PurviewTypeLibrary.System.Boolean.AsTypeReference()); } ); } @@ -633,17 +612,17 @@ static void WriteEventAttribute(CodeWriter writer, TypeIdentity type) { Parameters = [ - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()), + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), new ParameterDeclarationOptions( "useRecordExceptionRules", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ) { DefaultValue = "true", }, new ParameterDeclarationOptions( "recordExceptionAsEscaped", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ) { DefaultValue = "true", @@ -667,13 +646,13 @@ static void WriteEventAttribute(CodeWriter writer, TypeIdentity type) WritePublicProperty( body, "UseRecordExceptionRules", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); WritePublicProperty( body, "RecordExceptionAsEscaped", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); WritePublicProperty(body, "StatusCode", statusCode.AsTypeReference()); @@ -736,7 +715,10 @@ static void WriteLoggerAttribute(CodeWriter writer, TypeIdentity type) Parameters = [ new ParameterDeclarationOptions("defaultLevel", logLevel.AsTypeReference()), - new ParameterDeclarationOptions("customPrefix", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions( + "customPrefix", + PurviewTypeLibrary.System.String.AsTypeReference() + ) { DefaultValue = "null", }, @@ -788,12 +770,12 @@ static void WriteLogAttribute(CodeWriter writer, TypeIdentity type) new ParameterDeclarationOptions("level", logLevel.AsTypeReference()), new ParameterDeclarationOptions( "messageTemplate", - TypeLibrary.System.String.AsTypeReference() + PurviewTypeLibrary.System.String.AsTypeReference() ) { DefaultValue = "null", }, - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, @@ -811,16 +793,19 @@ static void WriteLogAttribute(CodeWriter writer, TypeIdentity type) { Parameters = [ - new ParameterDeclarationOptions("eventId", TypeLibrary.System.Int32.AsTypeReference()), + new ParameterDeclarationOptions( + "eventId", + PurviewTypeLibrary.System.Int32.AsTypeReference() + ), new ParameterDeclarationOptions("level", logLevel.AsTypeReference()), new ParameterDeclarationOptions( "messageTemplate", - TypeLibrary.System.String.AsTypeReference() + PurviewTypeLibrary.System.String.AsTypeReference() ) { DefaultValue = "null", }, - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, @@ -842,8 +827,8 @@ static void WriteLogAttribute(CodeWriter writer, TypeIdentity type) $"{logLevel.RenderFullName}.Information" ); WriteNullableStringProperty(body, "MessageTemplate"); - WritePublicProperty(body, "EventId", TypeLibrary.System.Int32.AsTypeReference().Nullable()); - WritePublicProperty(body, "Name", TypeLibrary.System.String.AsTypeReference().Nullable()); + WritePublicProperty(body, "EventId", PurviewTypeLibrary.System.Int32.MakeNullable(writer)); + WritePublicProperty(body, "Name", PurviewTypeLibrary.System.String.MakeNullable(writer)); WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); }, wrapInExcludeLoggingGuard: true @@ -867,12 +852,12 @@ static void WriteSpecificLogAttribute(CodeWriter writer, TypeIdentity type) [ new ParameterDeclarationOptions( "messageTemplate", - TypeLibrary.System.String.AsTypeReference() + PurviewTypeLibrary.System.String.AsTypeReference() ) { DefaultValue = "null", }, - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, @@ -889,15 +874,18 @@ static void WriteSpecificLogAttribute(CodeWriter writer, TypeIdentity type) { Parameters = [ - new ParameterDeclarationOptions("eventId", TypeLibrary.System.Int32.AsTypeReference()), + new ParameterDeclarationOptions( + "eventId", + PurviewTypeLibrary.System.Int32.AsTypeReference() + ), new ParameterDeclarationOptions( "messageTemplate", - TypeLibrary.System.String.AsTypeReference() + PurviewTypeLibrary.System.String.AsTypeReference() ) { DefaultValue = "null", }, - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, @@ -912,7 +900,7 @@ static void WriteSpecificLogAttribute(CodeWriter writer, TypeIdentity type) ); WriteNullableStringProperty(body, "MessageTemplate"); - WritePublicProperty(body, "EventId", TypeLibrary.System.Int32.AsTypeReference().Nullable()); + WritePublicProperty(body, "EventId", PurviewTypeLibrary.System.Int32.MakeNullable(writer)); WriteNullableStringProperty(body, "Name"); WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); }, @@ -935,7 +923,7 @@ static void WriteExpandEnumerableAttribute(CodeWriter writer, TypeIdentity type) [ new ParameterDeclarationOptions( "maximumValueCount", - TypeLibrary.System.Int32.AsTypeReference() + PurviewTypeLibrary.System.Int32.AsTypeReference() ) { DefaultValue = "5", @@ -946,7 +934,7 @@ static void WriteExpandEnumerableAttribute(CodeWriter writer, TypeIdentity type) ); body.XmlSummary("Gets or sets the maximum number of values to include when expanding an enumerable."); - WritePublicProperty(body, "MaximumValueCount", TypeLibrary.System.Int32.AsTypeReference()); + WritePublicProperty(body, "MaximumValueCount", PurviewTypeLibrary.System.Int32.AsTypeReference()); }, wrapInExcludeLoggingGuard: true ); @@ -971,7 +959,10 @@ static void WriteMeterGenerationAttribute(CodeWriter writer, TypeIdentity type) { Parameters = [ - new ParameterDeclarationOptions("meterName", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions( + "meterName", + PurviewTypeLibrary.System.String.AsTypeReference() + ) { DefaultValue = "null", }, @@ -981,21 +972,21 @@ static void WriteMeterGenerationAttribute(CodeWriter writer, TypeIdentity type) }, new ParameterDeclarationOptions( "instrumentPrefix", - TypeLibrary.System.String.AsTypeReference() + PurviewTypeLibrary.System.String.AsTypeReference() ) { DefaultValue = "null", }, new ParameterDeclarationOptions( "lowercaseInstrumentName", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ) { DefaultValue = "true", }, new ParameterDeclarationOptions( "lowercaseTagKeys", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ) { DefaultValue = "true", @@ -1024,10 +1015,15 @@ static void WriteMeterGenerationAttribute(CodeWriter writer, TypeIdentity type) WritePublicProperty( body, "LowercaseInstrumentName", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WritePublicProperty( + body, + "LowercaseTagKeys", + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); - WritePublicProperty(body, "LowercaseTagKeys", TypeLibrary.System.Boolean.AsTypeReference(), "true"); } ); } @@ -1048,16 +1044,21 @@ static void WriteMeterAttribute(CodeWriter writer, TypeIdentity type) WritePublicProperty( body, "IncludeAssemblyInstrumentPrefix", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); WritePublicProperty( body, "LowercaseInstrumentName", - TypeLibrary.System.Boolean.AsTypeReference(), + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + "true" + ); + WritePublicProperty( + body, + "LowercaseTagKeys", + PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); - WritePublicProperty(body, "LowercaseTagKeys", TypeLibrary.System.Boolean.AsTypeReference(), "true"); } ); } @@ -1096,7 +1097,7 @@ static void WriteCounterLikeAttribute(CodeWriter writer, TypeIdentity type) [ new ParameterDeclarationOptions( "autoIncrement", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ), ], }, @@ -1104,7 +1105,7 @@ static void WriteCounterLikeAttribute(CodeWriter writer, TypeIdentity type) ); WriteNameUnitDescriptionConstructor(body, type, appendAutoIncrement: true); - WritePublicProperty(body, "AutoIncrement", TypeLibrary.System.Boolean.AsTypeReference()); + WritePublicProperty(body, "AutoIncrement", PurviewTypeLibrary.System.Boolean.AsTypeReference()); WriteNullableStringProperty(body, "Name"); WriteNullableStringProperty(body, "Unit"); WriteNullableStringProperty(body, "Description"); @@ -1123,11 +1124,15 @@ static void WriteObservableCounterLikeAttribute(CodeWriter writer, TypeIdentity WriteEmptyConstructor(body, type); WriteNameUnitDescriptionConstructor(body, type, appendThrowOnAlreadyInitialized: true); - WritePublicProperty(body, "AutoIncrement", TypeLibrary.System.Boolean.AsTypeReference()); + WritePublicProperty(body, "AutoIncrement", PurviewTypeLibrary.System.Boolean.AsTypeReference()); WriteNullableStringProperty(body, "Name"); WriteNullableStringProperty(body, "Unit"); WriteNullableStringProperty(body, "Description"); - WritePublicProperty(body, "ThrowOnAlreadyInitialized", TypeLibrary.System.Boolean.AsTypeReference()); + WritePublicProperty( + body, + "ThrowOnAlreadyInitialized", + PurviewTypeLibrary.System.Boolean.AsTypeReference() + ); } ); } @@ -1164,23 +1169,23 @@ bool appendThrowOnAlreadyInitialized { var parameters = ImmutableArray.Empty; parameters = parameters.Add( - new ParameterDeclarationOptions("name", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) ); parameters = parameters.Add( - new ParameterDeclarationOptions("unit", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions("unit", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", } ); parameters = parameters.Add( - new ParameterDeclarationOptions("description", TypeLibrary.System.String.AsTypeReference()) + new ParameterDeclarationOptions("description", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", } ); if (appendAutoIncrement) parameters = parameters.Add( - new ParameterDeclarationOptions("autoIncrement", TypeLibrary.System.Boolean.AsTypeReference()) + new ParameterDeclarationOptions("autoIncrement", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "false", } @@ -1189,7 +1194,7 @@ bool appendThrowOnAlreadyInitialized parameters = parameters.Add( new ParameterDeclarationOptions( "throwOnAlreadyInitialized", - TypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference() ) { DefaultValue = "false", @@ -1205,82 +1210,65 @@ bool appendThrowOnAlreadyInitialized static void WriteTargetsEnum(CodeWriter writer, TypeIdentity type) { - WriteHeader(writer); - WriteFileScopedNamespace(writer); - - writer.XmlSummary("Determines which telemetry targets a parameter is excluded from."); - writer.WriteEnum( - new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) - { - Attributes = [new AttributeDeclarationOptions(new TypeIdentity("FlagsAttribute", "System"))], - }, - new EnumFieldDeclarationOptions("None", 0), - new EnumFieldDeclarationOptions("Activities", 1), - new EnumFieldDeclarationOptions("Logging", 2), - new EnumFieldDeclarationOptions("Metrics", 4), - new EnumFieldDeclarationOptions("All", "Activities | Logging | Metrics") - ); + writer + .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .XmlSummary("Determines which telemetry targets a parameter is excluded from.") + .WriteEnum( + new(type.Name, TypeDeclarationAccessibility.Public) + { + Attributes = [new AttributeDeclarationOptions(new TypeIdentity("FlagsAttribute", "System"))], + }, + new("None", 0), + new("Activities", 1), + new("Logging", 2), + new("Metrics", 4), + new("All", "Activities | Logging | Metrics") + ); } static void WriteNamingConventionEnum(CodeWriter writer, TypeIdentity type) { - WriteHeader(writer); - WriteFileScopedNamespace(writer); - - writer.XmlSummary("Determines the naming convention used for generated telemetry names."); - writer.WriteEnum( - new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), - new EnumFieldDeclarationOptions("Legacy", 0), - new EnumFieldDeclarationOptions("OpenTelemetry", 1) - ); + writer + .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .XmlSummary("Determines the naming convention used for generated telemetry names.") + .WriteEnum(new(type.Name, TypeDeclarationAccessibility.Public), new("Legacy", 0), new("OpenTelemetry", 1)); } static void WriteLogPrefixTypeEnum(CodeWriter writer, TypeIdentity type) { - WriteHeader(writer); writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); - WriteFileScopedNamespace(writer); - - writer.XmlSummary("Determines the mode used to generate or override the prefix for the log entry."); - writer.WriteEnum( - new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), - new EnumFieldDeclarationOptions("Default", 0), - new EnumFieldDeclarationOptions("Interface", 1), - new EnumFieldDeclarationOptions("Class", 2), - new EnumFieldDeclarationOptions("Custom", 3), - new EnumFieldDeclarationOptions("TrimmedClassName", 4) - ); + writer + .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .XmlSummary("Determines the mode used to generate or override the prefix for the log entry.") + .WriteEnum( + new(type.Name, TypeDeclarationAccessibility.Public), + new("Default", 0), + new("Interface", 1), + new("Class", 2), + new("Custom", 3), + new("TrimmedClassName", 4) + ); writer.WriteLine("#endif"); } static void WriteLoggerGenerationModeEnum(CodeWriter writer, TypeIdentity type) { - WriteHeader(writer); writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); - WriteFileScopedNamespace(writer); - - writer.XmlSummary("Controls the generation mode used for log methods."); - writer.WriteEnum( - new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), - new EnumFieldDeclarationOptions("Auto", 0), - new EnumFieldDeclarationOptions("V1", 1), - new EnumFieldDeclarationOptions("V2", 2) - ); + + writer + .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .XmlSummary("Controls the generation mode used for log methods.") + .WriteEnum(new(type.Name, TypeDeclarationAccessibility.Public), new("Auto", 0), new("V1", 1), new("V2", 2)); writer.WriteLine("#endif"); } static void WriteMeterNameGenerationTypeEnum(CodeWriter writer, TypeIdentity type) { - WriteHeader(writer); - WriteFileScopedNamespace(writer); - - writer.XmlSummary("Determines how meter names are generated when not explicitly specified."); - writer.WriteEnum( - new TypeDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), - new EnumFieldDeclarationOptions("OpenTelemetry", 0), - new EnumFieldDeclarationOptions("DotNet", 1) - ); + writer + .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .XmlSummary("Determines how meter names are generated when not explicitly specified.") + .WriteEnum(new(type.Name, TypeDeclarationAccessibility.Public), new("OpenTelemetry", 0), new("DotNet", 1)); } } diff --git a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs index 378bd762..cc3c6b34 100644 --- a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs @@ -7,14 +7,9 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class LoggerGenTargetClassEmitter { - static void EmitMethods( - LoggerTarget target, - CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable - ) + static void EmitMethods(LoggerOutputContext output, CodeWriter writer, SourceProductionContext context) { + var target = output.Target; foreach (var methodTarget in target.LogMethods) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -24,7 +19,7 @@ bool emitNullable // HasLogPropertiesAndExpandEnumerable stubs have IsValid=false; report TSG2006 here. if (methodTarget.HasLogPropertiesAndExpandEnumerable) { - LoggerTargetClassEmitter.EmitThrowStub(writer, methodTarget, emitNullable); + LoggerTargetClassEmitter.EmitThrowStub(writer, methodTarget); continue; } @@ -36,21 +31,21 @@ bool emitNullable ) ) { - LoggerTargetClassEmitter.EmitThrowStub(writer, methodTarget, emitNullable); + LoggerTargetClassEmitter.EmitThrowStub(writer, methodTarget); } continue; } if (methodTarget.UnknownReturnType) { - LoggerTargetClassEmitter.EmitThrowStub(writer, methodTarget, emitNullable); + LoggerTargetClassEmitter.EmitThrowStub(writer, methodTarget); continue; // Diagnostic already reported in EmitFields } // Report warning for Activity parameter without Activity target if (methodTarget.TargetGenerationState.ActivityParameterWithoutTarget != null) { - logger?.Debug( + output.Context.Debug( $"Activity parameter '{methodTarget.TargetGenerationState.ActivityParameterWithoutTarget}' on {methodTarget.MethodName} has no Activity target." ); } @@ -59,24 +54,23 @@ bool emitNullable // Must be checked before V1/V2 dispatch since V1 returns early. if (methodTarget.IsScoped && methodTarget.HasExplicitLevel) { - logger?.Diagnostic("Scoped method should not have an explicit log level."); + output.Context.Diagnostic("Scoped method should not have an explicit log level."); } - EmitMethod(writer, methodTarget, context, logger, emitNullable); + EmitMethod(output, methodTarget, writer, context); } } static void EmitMethod( - CodeWriter writer, + LoggerOutputContext output, LogMethodTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable + CodeWriter writer, + SourceProductionContext context ) { context.CancellationToken.ThrowIfCancellationRequested(); - logger?.Debug($"Building logging method: {methodTarget.MethodName}"); + output.Context.Debug($"Building logging method: {methodTarget.MethodName}"); if (methodTarget.UseV1Generation) { @@ -86,7 +80,7 @@ bool emitNullable && methodTarget.ParameterCountSansException <= PropertyLibrary.Logging.MaxNonExceptionParameters ) { - LoggerTargetClassEmitter.EmitLogActionMethod(writer, methodTarget, context, logger, emitNullable); + LoggerTargetClassEmitter.EmitLogActionMethod(output, methodTarget, writer, context); return; } @@ -111,9 +105,7 @@ bool emitNullable var methodName = generatePrivateLogging ? methodTarget.MethodName + "_Logging" : methodTarget.MethodName; var returnType = methodTarget.IsScoped - ? emitNullable - ? TypeLibrary.System.IDisposable.AsTypeReference().Nullable() - : TypeLibrary.System.IDisposable.AsTypeReference() + ? TypeLibrary.System.IDisposable.AsTypeReference().Nullable(writer) : PurviewTypeLibrary.System.Void.AsTypeReference(); writer.NewLine(); @@ -178,19 +170,19 @@ bool emitNullable // Output the state here... if (!useTypedState && !useScopedTypedState) { - EmitStateContent(writer, methodTarget, stateVarName, existingParamNames, context, logger, emitNullable); + EmitStateContent(output, writer, methodTarget, stateVarName, existingParamNames, context); } if (methodTarget.IsScoped) EmitScopedBody(writer, methodTarget, stateVarName, existingParamNames, useScopedTypedState, context); else - EmitNonScopedBody(writer, methodTarget, stateVarName, existingParamNames, useTypedState, emitNullable); + EmitNonScopedBody(writer, methodTarget, stateVarName, existingParamNames, useTypedState); } // Generate public delegating method if Logging owns it if (generatePublicDelegator) { - EmitPublicLoggingDelegatingMethod(writer, methodTarget, context, logger, emitNullable); + EmitPublicLoggingDelegatingMethod(output, methodTarget, writer, context); } } @@ -283,8 +275,7 @@ static void EmitNonScopedBody( LogMethodTarget methodTarget, string stateVarName, List existingParamNames, - bool useTypedState, - bool emitNullable + bool useTypedState ) { var expressionStateVarName = FindUniqueName("s", existingParamNames); @@ -293,13 +284,7 @@ bool emitNullable if (useTypedState) { - EmitTypedStateLogCall( - writer, - methodTarget, - expressionStateVarName, - expressionExceptionVarName, - emitNullable - ); + EmitTypedStateLogCall(writer, methodTarget, expressionStateVarName, expressionExceptionVarName); return; } @@ -319,7 +304,11 @@ bool emitNullable // Log level .Write(methodTarget.MSLevel.WithComma(andSpace: false)) // Event Id - .Write(emitNullable ? "new (" : "new " + TypeLibrary.Logging.MicrosoftExtensions.EventId + "(") + .Write( + writer.IsNullableContextEnabled is null or true + ? "new (" + : "new " + TypeLibrary.Logging.MicrosoftExtensions.EventId + "(" + ) .Write(eventId.ToString(CultureInfo.InvariantCulture)) .Write(", nameof(") .Write(methodTarget.LogName) @@ -328,15 +317,15 @@ bool emitNullable .Write(stateVarName.WithComma(andSpace: false)) // Exception .Write(methodTarget.ExceptionParameter.OrNullKeyword().WithComma(andSpace: false)); + // Message Template - if (emitNullable) - writer - .Write(emitNullable ? "static string (" : "(") - .Write(expressionStateVarName) - .Write(", ") - .Write(expressionExceptionVarName ?? "_") - .WriteLine(") =>") - .WriteLine("{"); + writer + .Write(writer.IsNullableContextEnabled is null or true ? "static string (" : "(") + .Write(expressionStateVarName) + .Write(", ") + .Write(expressionExceptionVarName ?? "_") + .WriteLine(") =>") + .WriteLine("{"); if (variables.Length > 0) { @@ -366,8 +355,7 @@ static void EmitTypedStateLogCall( CodeWriter writer, LogMethodTarget methodTarget, string expressionStateVarName, - string? expressionExceptionVarName, - bool emitNullable + string? expressionExceptionVarName ) { // Typed state struct approach: zero boxing, no ThreadLocalState. @@ -387,7 +375,11 @@ [.. methodTarget.Parameters] .Write(PropertyLibrary.Logging.LoggerFieldName) .WriteLine(".Log(") .Write(methodTarget.MSLevel.WithComma(andSpace: false)) - .Write(emitNullable ? "new (" : "new " + TypeLibrary.Logging.MicrosoftExtensions.EventId + "(") + .Write( + writer.IsNullableContextEnabled is null or true + ? "new (" + : "new " + TypeLibrary.Logging.MicrosoftExtensions.EventId + "(" + ) .Write(eventId.ToString(CultureInfo.InvariantCulture)) .Write(", nameof(") .Write(methodTarget.LogName) @@ -406,40 +398,38 @@ [.. methodTarget.Parameters] writer.WriteLine("),"); writer.Write(methodTarget.ExceptionParameter.OrNullKeyword().WithComma(andSpace: false)); - if (emitNullable) - writer - .Write("static string (") - .Write(expressionStateVarName) - .Write(", ") - .Write(expressionExceptionVarName ?? "_") - .WriteLine(") =>") - .WriteLine("{") - .WriteLine("#if NET") - .Write("return string.Create(global::System.Globalization.CultureInfo.InvariantCulture, $") - .Write(interpolatedMessage.Wrap()) - .WriteLine(");") - .WriteLine("#else") - .Write("return global::System.FormattableString.Invariant($") - .Write(interpolatedMessage.Wrap()) - .WriteLine(");") - .WriteLine("#endif") - .Write("}") - .Write(");"); + writer + .Write(writer.IsNullableContextEnabled is null or true ? "static string (" : "(") + .Write(expressionStateVarName) + .Write(", ") + .Write(expressionExceptionVarName ?? "_") + .WriteLine(") =>") + .WriteLine("{") + .WriteLine("#if NET") + .Write("return string.Create(global::System.Globalization.CultureInfo.InvariantCulture, $") + .Write(interpolatedMessage.Wrap()) + .WriteLine(");") + .WriteLine("#else") + .Write("return global::System.FormattableString.Invariant($") + .Write(interpolatedMessage.Wrap()) + .WriteLine(");") + .WriteLine("#endif") + .Write("}") + .Write(");"); writer.NewLine(); } static void EmitStateContent( + LoggerOutputContext output, CodeWriter writer, LogMethodTarget methodTarget, string stateVarName, List existingParamNames, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable = true + SourceProductionContext context ) { - logger?.Debug("Emitting state content"); + output.Context.Debug("Emitting state content"); // +1 for the OriginalFormat entry. var reservationCount = methodTarget.TotalParameterCount + 1; @@ -469,14 +459,7 @@ static void EmitStateContent( .NewLine(); // Original format is always at 0. - OutputState( - writer, - stateVarName, - "{OriginalFormat}".Wrap(), - methodTarget.MessageTemplate.Wrap(), - 0, - emitNullable: emitNullable - ); + OutputState(writer, stateVarName, "{OriginalFormat}".Wrap(), methodTarget.MessageTemplate.Wrap(), 0); var idx = 0; List? postSetProperties = null; @@ -493,15 +476,7 @@ static void EmitStateContent( var isEnumerable = parameter.IsArray || parameter.IsIEnumerable; // Need to match the name against the value. - OutputState( - writer, - stateVarName, - parameter.Name.Wrap(), - parameter.Name, - ++idx, - isEnumerable: isEnumerable, - emitNullable: emitNullable - ); + OutputState(writer, stateVarName, parameter.Name.Wrap(), parameter.Name, ++idx, isEnumerable: isEnumerable); if (isEnumerable) { @@ -509,7 +484,7 @@ static void EmitStateContent( { postSetProperties ??= []; postSetProperties.Add( - OutputExpandedEnumerable(writer, stateVarName, parameter, context, existingParamNames, logger) + OutputExpandedEnumerable(writer, stateVarName, parameter, context, existingParamNames, output) ); } } @@ -655,7 +630,7 @@ static string OutputExpandedEnumerable( LogParameterTarget parameter, SourceProductionContext context, List existingParamNames, - ISourceGenLogger? logger + LoggerOutputContext output ) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -675,7 +650,7 @@ static string OutputExpandedEnumerable( if (maxCount > PropertyLibrary.Logging.UnboundedIEnumerableMaxCountBeforeDiagnostic) { - logger?.Diagnostic($"Identified {parameter.Name} that has a large unbounded ienumerable max."); + output.Context.Diagnostic($"Identified {parameter.Name} that has a large unbounded ienumerable max."); } snippet @@ -870,14 +845,9 @@ LogParameterTarget[] parameters return escapedTemplate; } - static void EmitLogStateStructs( - LoggerTarget target, - CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? _, - bool emitNullable - ) + static void EmitLogStateStructs(LoggerOutputContext output, CodeWriter writer, SourceProductionContext context) { + var target = output.Target; foreach (var methodTarget in target.LogMethods) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -896,26 +866,20 @@ bool emitNullable continue; if (methodTarget.IsScoped) - EmitScopeStateStruct(writer, methodTarget, context, emitNullable); + EmitScopeStateStruct(writer, methodTarget, context); else - EmitLogStateStruct(writer, methodTarget, context, emitNullable); + EmitLogStateStruct(writer, methodTarget, context); } } - static void EmitLogStateStruct( - CodeWriter writer, - LogMethodTarget methodTarget, - SourceProductionContext context, - bool emitNullable - ) + static void EmitLogStateStruct(CodeWriter writer, LogMethodTarget methodTarget, SourceProductionContext context) { var nonExceptionParams = methodTarget.ParametersSansException; var structName = methodTarget.MethodName + "_LogState"; var count = nonExceptionParams.Count + 1; // +1 for {OriginalFormat} - var kvpType = emitNullable - ? "global::System.Collections.Generic.KeyValuePair" - : "global::System.Collections.Generic.KeyValuePair"; + var kvpType = + $"global::System.Collections.Generic.KeyValuePair"; var iReadOnlyListType = $"global::System.Collections.Generic.IReadOnlyList<{kvpType}>"; var ienumeratorType = $"global::System.Collections.Generic.IEnumerator<{kvpType}>"; var ienumerableKvpType = $"global::System.Collections.Generic.IEnumerable<{kvpType}>"; @@ -991,7 +955,7 @@ bool emitNullable using (writer.OpenBlockScope()) { - if (emitNullable) + if (writer.IsNullableContextEnabled is null or true) { writer.WriteLine("get => index switch {"); writer.Indent(); @@ -1045,15 +1009,7 @@ bool emitNullable } } - EmitStructEnumerator( - writer, - structName, - kvpType, - ienumeratorType, - ienumerableType, - ienumerableKvpType, - emitNullable - ); + EmitStructEnumerator(writer, structName, kvpType, ienumeratorType, ienumerableType, ienumerableKvpType); } writer.NewLine(); @@ -1065,13 +1021,11 @@ static void EmitStructEnumerator( string kvpType, string ienumeratorType, string ienumerableType, - string ienumerableKvpType, - bool emitNullable + string ienumerableKvpType ) { - var currentPropertyType = emitNullable - ? "object? global::System.Collections.IEnumerator.Current => Current;" - : "object global::System.Collections.IEnumerator.Current => Current;"; + var currentPropertyType = + $"{PurviewTypeLibrary.System.Object.MakeNullable(writer)} global::System.Collections.IEnumerator.Current => Current;"; writer.NewLine(); using ( @@ -1129,20 +1083,14 @@ bool emitNullable .Write($"{ienumerableType} global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();"); } - static void EmitScopeStateStruct( - CodeWriter writer, - LogMethodTarget methodTarget, - SourceProductionContext context, - bool emitNullable - ) + static void EmitScopeStateStruct(CodeWriter writer, LogMethodTarget methodTarget, SourceProductionContext context) { var nonExceptionParams = methodTarget.ParametersSansException; var structName = methodTarget.MethodName + "_ScopeState"; var count = nonExceptionParams.Count + 1; // +1 for {OriginalFormat} - var kvpType = emitNullable - ? "global::System.Collections.Generic.KeyValuePair" - : "global::System.Collections.Generic.KeyValuePair"; + var kvpType = + $"global::System.Collections.Generic.KeyValuePair"; var iReadOnlyListType = $"global::System.Collections.Generic.IReadOnlyList<{kvpType}>"; var ienumeratorType = $"global::System.Collections.Generic.IEnumerator<{kvpType}>"; var ienumerableKvpType = $"global::System.Collections.Generic.IEnumerable<{kvpType}>"; @@ -1155,7 +1103,7 @@ bool emitNullable new TypeDeclarationOptions(structName, TypeDeclarationAccessibility.Private) { IsReadOnly = true, - Interfaces = [new TypeReference(new TypeIdentity(iReadOnlyListType, null))], + Interfaces = [new TypeReference(new(iReadOnlyListType, null))], IncludeGeneratedAttributes = false, } ) @@ -1242,7 +1190,7 @@ [.. methodTarget.Parameters] using (writer.OpenBlockScope()) { - if (emitNullable) + if (writer.IsNullableContextEnabled is null or true) { writer.WriteLine("get => index switch {"); writer.Indent(); @@ -1296,44 +1244,29 @@ [.. methodTarget.Parameters] } } - EmitStructEnumerator( - writer, - structName, - kvpType, - ienumeratorType, - ienumerableType, - ienumerableKvpType, - emitNullable - ); + EmitStructEnumerator(writer, structName, kvpType, ienumeratorType, ienumerableType, ienumerableKvpType); } writer.NewLine(); } static void EmitPublicLoggingDelegatingMethod( - CodeWriter writer, + LoggerOutputContext output, LogMethodTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable + CodeWriter writer, + SourceProductionContext context ) { - logger?.Debug($"Building public delegating logging method: {methodTarget.MethodName}"); + output.Context.Debug($"Building public delegating logging method: {methodTarget.MethodName}"); writer.NewLine().Write("public "); // When Logging owns the public method (with Metrics), return void // (Logging without Activity means the return type is void or IDisposable for scoped) if (methodTarget.IsScoped) - { - writer.Write(TypeLibrary.System.IDisposable); - if (emitNullable) - writer.Write('?'); - } + writer.Write(TypeLibrary.System.IDisposable.MakeNullable(writer)); else - { - writer.Write(PropertyLibrary.System.VoidKeyword); - } + writer.Write(PurviewTypeLibrary.System.Void); writer.Write(' ').Write(methodTarget.MethodName).Write('('); diff --git a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.cs b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.cs index e0457e60..07e90dfd 100644 --- a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.cs @@ -9,25 +9,23 @@ static partial class LoggerGenTargetClassEmitter public static void GenerateImplementation(LoggerOutputContext output, SourceProductionContext context) { var target = output.Target; - var logger = output.Context.Logger; - var emitNullable = output.Context.Capabilities.SupportsNullableAnnotations; - var supportsIMeterFactory = output.Context.Capabilities.SupportsIMeterFactory; + output.Context.Debug($"Generating MS Gen-based logging class for: {target.FullyQualifiedName}"); - logger?.Debug($"Generating MS Gen-based logging class for: {target.FullyQualifiedName}"); - - var writer = output.Context.CreateCodeWriter(); - EmitterHelpers.WriteAutoGeneratedHeader(writer, emitNullable); + var writer = output.CreateWriter(); using (writer.WriteBlockNamespaceScope(target.ClassNamespace)) { List parentScopes = []; - foreach (var parent in target.ParentClasses) + if (target.TelemetryGeneration.TelemetryNamesNamespace == null) { - parentScopes.Add( - writer.WriteClassScope( - new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } - ) - ); + foreach (var parent in target.ParentClasses) + { + parentScopes.Add( + writer.WriteClassScope( + new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } + ) + ); + } } using ( @@ -41,7 +39,7 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd ) ) { - EmitFields(target, writer, context, logger, emitNullable); + EmitFields(output, writer, context); ConstructorEmitter.EmitCtor( GenerationType.Logging, @@ -50,13 +48,12 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd target.InterfaceType, writer, context, - logger, - supportsIMeterFactory + output.Context ); - EmitMethods(target, writer, context, logger, emitNullable); + EmitMethods(output, writer, context); - EmitLogStateStructs(target, writer, context, logger, emitNullable); + EmitLogStateStructs(output, writer, context); } foreach (var scope in parentScopes) @@ -66,25 +63,21 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd context.AddSource($"{target.FullyQualifiedName}.Logging.g.cs", writer); DependencyInjectionClassEmitter.GenerateImplementation( + output.CreateWriter(), GenerationType.Logging, target.TelemetryGeneration, target.GenerationType, target.ClassNameToGenerate, - target.InterfaceType.Identity.Name, - target.FullNamespace, + target.InterfaceType, context, output.Context ); } - static void EmitFields( - LoggerTarget target, - CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable - ) + static void EmitFields(LoggerOutputContext output, CodeWriter writer, SourceProductionContext context) { + var target = output.Target; + context.CancellationToken.ThrowIfCancellationRequested(); writer @@ -113,7 +106,7 @@ bool emitNullable // Multiple exceptions is always invalid, regardless of v1 or v2 generation. if (methodTarget.HasMultipleExceptions) { - logger?.Diagnostic("Method has multiple exception parameters, only a single one is permitted."); + output.Context.Diagnostic("Method has multiple exception parameters, only a single one is permitted."); continue; } @@ -122,16 +115,16 @@ bool emitNullable if (methodTarget.ParameterCountSansException > PropertyLibrary.Logging.MaxNonExceptionParameters) { - logger?.Diagnostic("Method has more than 6 parameters."); + output.Context.Diagnostic("Method has more than 6 parameters."); continue; } if (methodTarget.InferredErrorLevel) { - logger?.Diagnostic("Inferring error log level."); + output.Context.Diagnostic("Inferring error log level."); } - LoggerTargetClassEmitter.EmitLogActionField(writer, methodTarget, emitNullable); + LoggerTargetClassEmitter.EmitLogActionField(writer, methodTarget); } } } diff --git a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Fields.cs b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Fields.cs index d02053bd..16a6a459 100644 --- a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Fields.cs +++ b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Fields.cs @@ -7,14 +7,10 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class LoggerTargetClassEmitter { - static void EmitFields( - LoggerTarget target, - CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable - ) + static void EmitFields(LoggerOutputContext output, CodeWriter writer, SourceProductionContext context) { + var target = output.Target; + context.CancellationToken.ThrowIfCancellationRequested(); writer @@ -37,13 +33,13 @@ bool emitNullable { if (methodTarget.TargetGenerationState.RaiseMultiGenerationTargetsNotSupported) { - logger?.Debug( + output.Context.Debug( $"Identified {target.InterfaceType.Identity.Name}.{methodTarget.MethodName} as problematic as it has another target types." ); } else if (methodTarget.TargetGenerationState.RaiseInferenceNotSupportedWithMultiTargeting) { - logger?.Debug( + output.Context.Debug( $"Identified {target.InterfaceType.Identity.Name}.{methodTarget.MethodName} as problematic as it is inferred." ); } @@ -54,7 +50,7 @@ bool emitNullable // Report warning for Activity parameter without Activity target if (methodTarget.TargetGenerationState.ActivityParameterWithoutTarget != null) { - logger?.Debug( + output.Context.Debug( $"Activity parameter '{methodTarget.TargetGenerationState.ActivityParameterWithoutTarget}' on {methodTarget.MethodName} has no Activity target." ); } @@ -66,32 +62,32 @@ bool emitNullable if (methodTarget.HasMultipleExceptions) { - logger?.Diagnostic("Method has multiple exception parameters, only a single one is permitted."); + output.Context.Diagnostic("Method has multiple exception parameters, only a single one is permitted."); continue; } if (methodTarget.ParameterCountSansException > PropertyLibrary.Logging.MaxNonExceptionParameters) { - logger?.Diagnostic("Method has more than 6 parameters."); + output.Context.Diagnostic("Method has more than 6 parameters."); continue; } if (methodTarget.InferredErrorLevel) { - logger?.Diagnostic("Inferring error log level."); + output.Context.Diagnostic("Inferring error log level."); } - EmitLogActionField(writer, methodTarget, emitNullable); + EmitLogActionField(writer, methodTarget); } } - internal static void EmitLogActionField(CodeWriter writer, LogMethodTarget methodTarget, bool emitNullable = true) + internal static void EmitLogActionField(CodeWriter writer, LogMethodTarget methodTarget) { writer .Write("static readonly ") - .Write(methodTarget.IsScoped ? TypeLibrary.System.Func : TypeLibrary.System.Action) + .Write(methodTarget.IsScoped ? PurviewTypeLibrary.System.Func : PurviewTypeLibrary.System.Action) .Write('<') .Write(TypeLibrary.Logging.MicrosoftExtensions.ILogger) .Write(", "); @@ -101,16 +97,12 @@ internal static void EmitLogActionField(CodeWriter writer, LogMethodTarget metho if (methodTarget.IsScoped) { - writer.Write(TypeLibrary.System.IDisposable); - if (emitNullable) - writer.Write('?'); + writer.Write(TypeLibrary.System.IDisposable.MakeNullable(writer)); writer.Write("> "); } else { - writer.Write(TypeLibrary.System.Exception); - if (emitNullable) - writer.Write('?'); + writer.Write(TypeLibrary.System.Exception.MakeNullable(writer)); writer.Write("> "); } diff --git a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Methods.cs index f972fe05..16c932c8 100644 --- a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Methods.cs @@ -6,18 +6,16 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class LoggerTargetClassEmitter { - internal static void EmitThrowStub(CodeWriter writer, LogMethodTarget methodTarget, bool emitNullable = true) + internal static void EmitThrowStub(CodeWriter writer, LogMethodTarget methodTarget) { var returnType = methodTarget.IsScoped - ? emitNullable - ? TypeLibrary.System.IDisposable.AsTypeReference().Nullable() - : TypeLibrary.System.IDisposable.AsTypeReference() + ? TypeLibrary.System.IDisposable.AsTypeReference().Nullable(writer) : PurviewTypeLibrary.System.Void.AsTypeReference(); writer.NewLine(); using ( writer.WriteMethodScope( - new MethodDeclarationOptions(methodTarget.MethodName, returnType, TypeDeclarationAccessibility.Public) + new(methodTarget.MethodName, returnType, TypeDeclarationAccessibility.Public) { Parameters = [ @@ -36,14 +34,9 @@ internal static void EmitThrowStub(CodeWriter writer, LogMethodTarget methodTarg } } - static void EmitMethods( - LoggerTarget target, - CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable - ) + static void EmitMethods(LoggerOutputContext output, CodeWriter writer, SourceProductionContext context) { + var target = output.Target; foreach (var methodTarget in target.LogMethods) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -58,44 +51,43 @@ bool emitNullable ) ) { - EmitThrowStub(writer, methodTarget, emitNullable); + EmitThrowStub(writer, methodTarget); } continue; } if (methodTarget.UnknownReturnType) { - EmitThrowStub(writer, methodTarget, emitNullable); + EmitThrowStub(writer, methodTarget); continue; // Diagnostic already reported in EmitFields } if (methodTarget.HasMultipleExceptions) { - EmitThrowStub(writer, methodTarget, emitNullable); + EmitThrowStub(writer, methodTarget); continue; } if (methodTarget.ParameterCountSansException > PropertyLibrary.Logging.MaxNonExceptionParameters) { - EmitThrowStub(writer, methodTarget, emitNullable); + EmitThrowStub(writer, methodTarget); continue; } - EmitLogActionMethod(writer, methodTarget, context, logger, emitNullable); + EmitLogActionMethod(output, methodTarget, writer, context); } } internal static void EmitLogActionMethod( - CodeWriter writer, + LoggerOutputContext output, LogMethodTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable = true + CodeWriter writer, + SourceProductionContext context ) { context.CancellationToken.ThrowIfCancellationRequested(); - logger?.Debug($"Building logging method: {methodTarget.MethodName}"); + output.Context.Debug($"Building logging method: {methodTarget.MethodName}"); var isMultiTarget = methodTarget.TargetGenerationState.IsMultiTarget; var methodTargets = methodTarget.TargetGenerationState.MethodTargets; @@ -117,7 +109,7 @@ internal static void EmitLogActionMethod( var returnType = generatePrivateLogging || !methodTarget.IsScoped ? PurviewTypeLibrary.System.Void.AsTypeReference() - : TypeLibrary.System.IDisposable.AsTypeReference().Nullable(); + : TypeLibrary.System.IDisposable.AsTypeReference().Nullable(writer); writer.NewLine(); @@ -190,24 +182,21 @@ internal static void EmitLogActionMethod( // Generate public delegating method if Logging owns it if (generatePublicDelegator) { - EmitPublicLoggingDelegatingMethod(writer, methodTarget, context, logger, emitNullable); + EmitPublicLoggingDelegatingMethod(output, methodTarget, writer, context); } } static void EmitPublicLoggingDelegatingMethod( - CodeWriter writer, + LoggerOutputContext output, LogMethodTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable = true + CodeWriter writer, + SourceProductionContext context ) { - logger?.Debug($"Building public delegating logging method: {methodTarget.MethodName}"); + output.Context.Debug($"Building public delegating logging method: {methodTarget.MethodName}"); var returnType = methodTarget.IsScoped - ? emitNullable - ? TypeLibrary.System.IDisposable.AsTypeReference().Nullable() - : TypeLibrary.System.IDisposable.AsTypeReference() + ? TypeLibrary.System.IDisposable.AsTypeReference().Nullable(writer) : PurviewTypeLibrary.System.Void.AsTypeReference(); writer.NewLine(); diff --git a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.cs b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.cs index 058075b5..b49c315c 100644 --- a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.cs @@ -8,25 +8,22 @@ static partial class LoggerTargetClassEmitter public static void GenerateImplementation(LoggerOutputContext output, SourceProductionContext context) { var target = output.Target; - var logger = output.Context.Logger; - var emitNullable = output.Context.Capabilities.SupportsNullableAnnotations; - var supportsIMeterFactory = output.Context.Capabilities.SupportsIMeterFactory; - - logger?.Debug($"Generating logging class for: {target.FullyQualifiedName}"); - - var writer = output.Context.CreateCodeWriter(); - EmitterHelpers.WriteAutoGeneratedHeader(writer, emitNullable); + output.Context.Debug($"Generating logging class for: {target.FullyQualifiedName}"); + var writer = output.CreateWriter(); using (writer.WriteBlockNamespaceScope(target.ClassNamespace)) { List parentScopes = []; - foreach (var parent in target.ParentClasses) + if (target.TelemetryGeneration.TelemetryNamesNamespace == null) { - parentScopes.Add( - writer.WriteClassScope( - new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } - ) - ); + foreach (var parent in target.ParentClasses) + { + parentScopes.Add( + writer.WriteClassScope( + new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } + ) + ); + } } using ( @@ -40,7 +37,7 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd ) ) { - EmitFields(target, writer, context, logger, emitNullable); + EmitFields(output, writer, context); ConstructorEmitter.EmitCtor( GenerationType.Logging, @@ -49,11 +46,10 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd target.InterfaceType, writer, context, - logger, - supportsIMeterFactory + output.Context ); - EmitMethods(target, writer, context, logger, emitNullable); + EmitMethods(output, writer, context); } foreach (var scope in parentScopes) @@ -63,12 +59,12 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd context.AddSource($"{target.FullyQualifiedName}.Logging.g.cs", writer); DependencyInjectionClassEmitter.GenerateImplementation( + output.CreateWriter(), GenerationType.Logging, target.TelemetryGeneration, target.GenerationType, target.ClassNameToGenerate, - target.InterfaceType.Identity.Name, - target.FullNamespace, + target.InterfaceType, context, output.Context ); diff --git a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Fields.cs b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Fields.cs index 99562fdc..39156f24 100644 --- a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Fields.cs +++ b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Fields.cs @@ -7,17 +7,16 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class MeterTargetClassEmitter { static void EmitFields( - MeterTarget target, + MeterOutputContext output, CodeWriter writer, SourceProductionContext context, - ISourceGenLogger? logger, - bool readonlyFields = false, - bool emitNullable = true + bool readonlyFields = false ) { + var target = output.Target; context.CancellationToken.ThrowIfCancellationRequested(); - logger?.Debug($"Emitting fields for {target.ClassNameToGenerate}"); + output.Context.Debug($"Emitting fields for {target.ClassNameToGenerate}"); // When metrics owns the constructor, emit readonly fields so the JIT can treat // them as immutable after construction and eliminate null checks in hot paths. @@ -37,7 +36,7 @@ static void EmitFields( .Write(TypeLibrary.Metrics.SystemDiagnostics.Meter) .Write(' ') .Write(MeterFieldName) - .WriteLine(emitNullable ? " = default!;" : " = default;") + .WriteLine(writer.IsNullableContextEnabled is null or true ? " = default!;" : " = default;") .NewLine(); } @@ -73,7 +72,7 @@ static void EmitFields( .Write(type) .Write(' ') .Write(method.FieldName) - .WriteLine(emitNullable ? " = default!;" : " = default;"); + .WriteLine(writer.IsNullableContextEnabled is null or true ? " = default!;" : " = default;"); } } } diff --git a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.InitializationMethod.cs b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.InitializationMethod.cs index 49b28574..89fc6efa 100644 --- a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.InitializationMethod.cs +++ b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.InitializationMethod.cs @@ -6,14 +6,10 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; partial class MeterTargetClassEmitter { - static void EmitInitializationMethod( - MeterTarget target, - CodeWriter writer, - SourceProductionContext context, - bool emitNullable, - bool supportsIMeterFactory - ) + static void EmitInitializationMethod(MeterOutputContext output, CodeWriter writer, SourceProductionContext context) { + var supportsIMeterFactory = output.Context.Capabilities.SupportsIMeterFactory; + context.CancellationToken.ThrowIfCancellationRequested(); writer.NewLine().Write("void ").Write(PropertyLibrary.Metrics.MeterInitializationMethod).Write('('); @@ -44,21 +40,18 @@ bool supportsIMeterFactory writer.NewLine(); - EmitInitializationBodyContent(target, writer, emitNullable, supportsIMeterFactory); + EmitInitializationBodyContent(output, writer); } } // Emits the inline constructor for the Metrics-only (and Activity+Metrics) case // where Metrics owns the constructor. Instrument fields are readonly, so we // inline the init body directly rather than delegating to InitializeMeters(). - static void EmitInlineConstructor( - MeterTarget target, - CodeWriter writer, - SourceProductionContext context, - bool emitNullable, - bool supportsIMeterFactory - ) + static void EmitInlineConstructor(MeterOutputContext output, CodeWriter writer, SourceProductionContext context) { + var target = output.Target; + var supportsIMeterFactory = output.Context.Capabilities.SupportsIMeterFactory; + context.CancellationToken.ThrowIfCancellationRequested(); writer.NewLine().Write("public ").Write(target.ClassNameToGenerate).Write('('); @@ -75,20 +68,17 @@ bool supportsIMeterFactory using (writer.OpenBlockScope()) { - EmitInitializationBodyContent(target, writer, emitNullable, supportsIMeterFactory); + EmitInitializationBodyContent(output, writer); } } - static void EmitInitializationBodyContent( - MeterTarget target, - CodeWriter writer, - bool emitNullable, - bool supportsIMeterFactory - ) + static void EmitInitializationBodyContent(MeterOutputContext output, CodeWriter writer) { + var target = output.Target; + var supportsIMeterFactory = output.Context.Capabilities.SupportsIMeterFactory; const string meterTagsVariableName = "meterTags"; - var dictType = GetDictionaryType(emitNullable); + var dictType = GetDictionaryType(writer); writer .Write((string)dictType) .Write(' ') @@ -131,10 +121,10 @@ bool supportsIMeterFactory } foreach (var method in target.InstrumentationMethods) - EmitInitialiseInstrumentVariable(method, writer, emitNullable); + EmitInitialiseInstrumentVariable(method, writer); } - static void EmitInitialiseInstrumentVariable(InstrumentTarget method, CodeWriter writer, bool emitNullable) + static void EmitInitialiseInstrumentVariable(InstrumentTarget method, CodeWriter writer) { if (!method.TargetGenerationState.IsValid) return; @@ -145,7 +135,7 @@ static void EmitInitialiseInstrumentVariable(InstrumentTarget method, CodeWriter var description = method.InstrumentAttribute?.Description?.Wrap() ?? PropertyLibrary.System.NullKeyword; var tagVariableName = Utilities.LowercaseFirstChar(method.MethodName) + "Tags"; - var dictType = GetDictionaryType(emitNullable); + var dictType = GetDictionaryType(writer); writer .Write((string)dictType) .Write(' ') diff --git a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Methods.cs index 4dda1014..0f753732 100644 --- a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Methods.cs @@ -26,15 +26,11 @@ static void EmitThrowStub(CodeWriter writer, InstrumentTarget methodTarget) writer.Write(") => throw new global::System.NotSupportedException();").NewLine(); } - static void EmitMethods( - MeterTarget target, - CodeWriter writer, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable - ) + static void EmitMethods(MeterOutputContext output, CodeWriter writer, SourceProductionContext context) { - EmitPartialMethods(writer, target, context, logger, emitNullable); + var target = output.Target; + + EmitPartialMethods(output, writer, context); foreach (var methodTarget in target.InstrumentationMethods) { @@ -58,28 +54,23 @@ bool emitNullable // Report warning for Activity parameter without Activity target if (methodTarget.TargetGenerationState.ActivityParameterWithoutTarget != null) { - logger?.Debug( + output.Context.Debug( $"Activity parameter '{methodTarget.TargetGenerationState.ActivityParameterWithoutTarget}' on {methodTarget.MethodName} has no Activity target." ); } - EmitMethod(writer, methodTarget, context, logger, emitNullable); + EmitMethod(output, methodTarget, writer, context); } } - static void EmitPartialMethods( - CodeWriter writer, - MeterTarget target, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable = true - ) + static void EmitPartialMethods(MeterOutputContext output, CodeWriter writer, SourceProductionContext context) { + var target = output.Target; context.CancellationToken.ThrowIfCancellationRequested(); - logger?.Debug($"Emitting partial method for populating tags: {PartialMeterTagsMethod}."); + output.Context.Debug($"Emitting partial method for populating tags: {PartialMeterTagsMethod}."); - var dictType = GetDictionaryType(emitNullable); + var dictType = GetDictionaryType(writer); writer .NewLine() .Write("partial void ") @@ -108,11 +99,10 @@ static void EmitPartialMethods( } static void EmitMethod( - CodeWriter writer, + MeterOutputContext output, InstrumentTarget methodTarget, - SourceProductionContext context, - ISourceGenLogger? logger, - bool emitNullable = true + CodeWriter writer, + SourceProductionContext context ) { context.CancellationToken.ThrowIfCancellationRequested(); @@ -126,7 +116,7 @@ static void EmitMethod( var loggingOwnsPublicMethod = !activityOwnsPublicMethod && methodTargets.HasFlag(GenerationType.Logging); var metricsOwnsPublicMethod = !activityOwnsPublicMethod && !loggingOwnsPublicMethod; - logger?.Debug($"Emitting instrument method: {methodTarget.MethodName}."); + output.Context.Debug($"Emitting instrument method: {methodTarget.MethodName}."); // For multi-target where Activity or Logging owns public method, generate private method var methodName = @@ -165,7 +155,7 @@ static void EmitMethod( if (methodTarget.IsObservable) EmitObservableInstrumentBody(writer, methodTarget, tagVariableName); else - EmitInstrumentBody(writer, methodTarget, tagVariableName, emitNullable); + EmitInstrumentBody(writer, methodTarget, tagVariableName); } } @@ -213,7 +203,7 @@ .. methodTarget.Parameters.Select(p => if (methodTarget.MeasurementParameter!.IsIEnumerable) type = TypeLibrary.System.GenericIEnumerable.MakeGeneric(type); - type = TypeLibrary.System.Func.MakeGeneric(type); + type = PurviewTypeLibrary.System.Func.MakeGeneric(type); return new ParameterDeclarationOptions(p.ParameterName, new TypeReference(type)); }), diff --git a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.cs b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.cs index fd99a4fc..20b93053 100644 --- a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.cs @@ -6,12 +6,10 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; static partial class MeterTargetClassEmitter { - static TypeReference GetDictionaryType(bool emitNullable) => + static TypeReference GetDictionaryType(CodeWriter writer) => TypeLibrary.System.Dictionary.MakeGeneric( - TypeLibrary.System.String.AsTypeReference(), - emitNullable - ? TypeLibrary.System.Object.AsTypeReference().Nullable() - : TypeLibrary.System.Object.AsTypeReference() + PurviewTypeLibrary.System.String.AsTypeReference(), + PurviewTypeLibrary.System.Object.AsTypeReference().Nullable(writer) ); const string MeterFieldName = "_meter"; @@ -20,25 +18,22 @@ static TypeReference GetDictionaryType(bool emitNullable) => public static void GenerateImplementation(MeterOutputContext output, SourceProductionContext context) { var target = output.Target; - var logger = output.Context.Logger; - var emitNullable = output.Context.Capabilities.SupportsNullableAnnotations; - var supportsIMeterFactory = output.Context.Capabilities.SupportsIMeterFactory; - - logger?.Debug($"Generating metric class for: {target.FullyQualifiedName}"); - - var writer = output.Context.CreateCodeWriter(); - EmitterHelpers.WriteAutoGeneratedHeader(writer, emitNullable); + output.Context.Debug($"Generating metric class for: {target.FullyQualifiedName}"); + var writer = output.CreateWriter(); using (writer.WriteBlockNamespaceScope(target.ClassNamespace)) { List parentScopes = []; - foreach (var parent in target.ParentClasses) + if (target.TelemetryGeneration.TelemetryNamesNamespace == null) { - parentScopes.Add( - writer.WriteClassScope( - new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } - ) - ); + foreach (var parent in target.ParentClasses) + { + parentScopes.Add( + writer.WriteClassScope( + new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } + ) + ); + } } using ( @@ -61,19 +56,10 @@ public static void GenerateImplementation(MeterOutputContext output, SourceProdu target.GenerationType ); - EmitFields( - target, - writer, - context, - logger, - readonlyFields: metricsOwnsConstructor, - emitNullable: emitNullable - ); + EmitFields(output, writer, context, readonlyFields: metricsOwnsConstructor); if (metricsOwnsConstructor) - { - EmitInlineConstructor(target, writer, context, emitNullable, supportsIMeterFactory); - } + EmitInlineConstructor(output, writer, context); else { ConstructorEmitter.EmitCtor( @@ -83,14 +69,13 @@ public static void GenerateImplementation(MeterOutputContext output, SourceProdu target.InterfaceType, writer, context, - logger, - supportsIMeterFactory + output.Context ); - EmitInitializationMethod(target, writer, context, emitNullable, supportsIMeterFactory); + EmitInitializationMethod(output, writer, context); } - EmitMethods(target, writer, context, logger, emitNullable); + EmitMethods(output, writer, context); } foreach (var scope in parentScopes) @@ -100,12 +85,12 @@ public static void GenerateImplementation(MeterOutputContext output, SourceProdu context.AddSource($"{target.FullyQualifiedName}.Metric.g.cs", writer); DependencyInjectionClassEmitter.GenerateImplementation( + output.CreateWriter(), GenerationType.Metrics, target.TelemetryGeneration, target.GenerationType, target.ClassNameToGenerate, - target.InterfaceType.Identity.Name, - target.FullNamespace, + target.InterfaceType, context, output.Context ); diff --git a/src/src/SourceGenerator/Emitters/TelemetryNamesEmitter.cs b/src/src/SourceGenerator/Emitters/TelemetryNamesEmitter.cs index f7c652bf..d3732ea3 100644 --- a/src/src/SourceGenerator/Emitters/TelemetryNamesEmitter.cs +++ b/src/src/SourceGenerator/Emitters/TelemetryNamesEmitter.cs @@ -1,64 +1,57 @@ using System.Collections.Immutable; using Microsoft.CodeAnalysis; -using Purview.Telemetry.SourceGenerator.Helpers; +using Purview.Telemetry.SourceGenerator.Records; namespace Purview.Telemetry.SourceGenerator.Emitters; static class TelemetryNamesEmitter { public static void GenerateClass( + TelemetryNamesOutputContext output, ImmutableArray meterNames, ImmutableArray activitySourceNames, string className, string? rootNamespace, - bool emitNullable, SourceProductionContext spc, - ISourceGenLogger? logger + GenerationContext generationContext ) { - logger?.Debug($"Generating Telemetry Names using class '{className}'."); - - var writer = new CodeWriter(GenerationSettings.Create()); - EmitterHelpers.WriteAutoGeneratedHeader(writer, emitNullable); + generationContext.Debug($"Generating Telemetry Names using class '{className}'."); + var writer = output.CreateWriter(); var hasNamespace = !string.IsNullOrWhiteSpace(rootNamespace); using (writer.WriteBlockNamespaceScope(rootNamespace)) { using ( writer.WriteClassScope( - new TypeDeclarationOptions(className, TypeDeclarationAccessibility.Internal) + new(className) { IsStatic = true, - IncludeGeneratedAttributes = false, + IncludeGeneratedAttributes = true, Attributes = [EmitterHelpers.EditorBrowsableAttribute()], } ) ) { - var stringArrayType = TypeLibrary.System.String.AsTypeReference().MakeArray(); - + var stringArrayType = PurviewTypeLibrary.System.String.AsTypeReference().MakeArray(); writer.WriteField( - new FieldDeclarationOptions("MeterNames", stringArrayType, TypeDeclarationAccessibility.Public) + new("MeterNames", stringArrayType, TypeDeclarationAccessibility.Public) { IsStatic = true, IsReadOnly = true, Initializer = BuildArrayInitializer(meterNames), - IncludeGeneratedAttributes = false, + IncludeGeneratedAttributes = true, } ); writer.WriteField( - new FieldDeclarationOptions( - "ActivitySourceNames", - stringArrayType, - TypeDeclarationAccessibility.Public - ) + new("ActivitySourceNames", stringArrayType, TypeDeclarationAccessibility.Public) { IsStatic = true, IsReadOnly = true, Initializer = BuildArrayInitializer(activitySourceNames), - IncludeGeneratedAttributes = false, + IncludeGeneratedAttributes = true, } ); } diff --git a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Activities.cs b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Activities.cs index 7bcd46c5..0db12697 100644 --- a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Activities.cs +++ b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Activities.cs @@ -22,7 +22,8 @@ IncrementalValueProvider> generationCon source: outputContexts, action: static (spc, output) => { - output.Context.Logger?.Debug($"Activity generation target: {output.Target.FullyQualifiedName}"); + output.Context.Debug($"Activity generation target: {output.Target.FullyQualifiedName}"); + RunSafely(spc, () => ActivitySourceTargetClassEmitter.GenerateImplementation(output, spc)); } ); diff --git a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Logging.cs b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Logging.cs index cb1e5361..ac5d087b 100644 --- a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Logging.cs +++ b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Logging.cs @@ -15,9 +15,9 @@ IncrementalValueProvider> generationCon // Register var loggerTargetsPredicate = IncrementalPipeline.ForAttributeWithMetadataName( context, - TemplateLibrary.Logging.LoggerAttribute.TypeInfo, + TypeLibrary.Logging.LoggerAttribute, transform: static (context, cancellationToken) => - PipelineHelpers.BuildLoggerTransform(context, null, cancellationToken), + PipelineHelpers.BuildLoggerTransform(context, cancellationToken), predicate: static (node, token) => PipelineHelpers.HasLoggerTargetAttribute(node, token), trackingName: $"{nameof(TelemetrySourceGenerator)}_Logging" ); @@ -33,7 +33,7 @@ IncrementalValueProvider> generationCon source: outputContexts, action: static (spc, output) => { - output.Context.Logger?.Debug($"Logger generation target: {output.Target.FullyQualifiedName}"); + output.Context.Debug($"Logger generation target: {output.Target.FullyQualifiedName}"); if (output.Target.UseMSLoggingTelemetryBasedGeneration) RunSafely(spc, () => LoggerGenTargetClassEmitter.GenerateImplementation(output, spc)); diff --git a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Metrics.cs b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Metrics.cs index 8944e7c1..cc476c13 100644 --- a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Metrics.cs +++ b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.Metrics.cs @@ -22,7 +22,7 @@ IncrementalValueProvider> generationCon source: outputContexts, action: static (spc, output) => { - output.Context.Logger?.Debug($"Meter generation target: {output.Target.FullyQualifiedName}"); + output.Context.Debug($"Meter generation target: {output.Target.FullyQualifiedName}"); RunSafely(spc, () => MeterTargetClassEmitter.GenerateImplementation(output, spc)); } ); diff --git a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.TelemetryNames.cs b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.TelemetryNames.cs index 67c800b7..f0bfcaec 100644 --- a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.TelemetryNames.cs +++ b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.TelemetryNames.cs @@ -41,8 +41,6 @@ IncrementalValueProvider> generationCon static void GenerateTelemetryNames(TelemetryNamesOutputContext output, SourceProductionContext spc) { - var logger = output.Context.Logger; - // Only generate if we have at least one target if (output.MeterTargets.IsEmpty && output.ActivityTargets.IsEmpty) { @@ -62,6 +60,7 @@ static void GenerateTelemetryNames(TelemetryNamesOutputContext output, SourcePro // Check if any target has GenerateTelemetryNamesClass set to false var generateClass = true; string? customClassName = null; + string? customNamespace = null; // Check meter targets for TelemetryGeneration settings foreach (var target in processedMeters) @@ -76,6 +75,11 @@ static void GenerateTelemetryNames(TelemetryNamesOutputContext output, SourcePro { customClassName = target.TelemetryGeneration.TelemetryNamesClassName; } + + if (target.TelemetryGeneration.TelemetryNamesNamespace != null && customNamespace == null) + { + customNamespace = target.TelemetryGeneration.TelemetryNamesNamespace; + } } // Check activity targets for TelemetryGeneration settings @@ -91,6 +95,11 @@ static void GenerateTelemetryNames(TelemetryNamesOutputContext output, SourcePro { customClassName = target.TelemetryGeneration.TelemetryNamesClassName; } + + if (target.TelemetryGeneration.TelemetryNamesNamespace != null && customNamespace == null) + { + customNamespace = target.TelemetryGeneration.TelemetryNamesNamespace; + } } if (!generateClass) @@ -121,13 +130,13 @@ static void GenerateTelemetryNames(TelemetryNamesOutputContext output, SourcePro spc, () => TelemetryNamesEmitter.GenerateClass( + output, meterNames, activitySourceNames, className!, - output.AssemblyName, - output.Context.Capabilities.SupportsNullableAnnotations, + customNamespace ?? output.AssemblyName, spc, - logger + output.Context ) ); } diff --git a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.cs b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.cs index a59b2dd8..dd291cb6 100644 --- a/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.cs +++ b/src/src/SourceGenerator/Generators/TelemetrySourceGenerator.cs @@ -28,10 +28,7 @@ static void RunSafely(SourceProductionContext spc, Action generate) catch (Exception ex) { spc.ReportDiagnostic( - DiagnosticInfo.Create( - TelemetryRules.ToDescriptor(DiagnosticLibrary.General.FatalExecutionDuringExecution), - ex.ToString() - ) + DiagnosticInfo.Create(DiagnosticLibrary.General.FatalExecutionDuringExecution.Descriptor, ex.ToString()) ); } } @@ -40,17 +37,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { // RegisterPostInitializationOutput (not RegisterSourceOutput) ensures ForAttributeWithMetadataName // can resolve attribute types before it runs. - context.RegisterPostInitializationOutput(ctx => - { - // Adds Microsoft.CodeAnalysis.EmbeddedAttribute to the compilation so generated marker - // types (decorated with [Microsoft.CodeAnalysis.Embedded]) are invisible to downstream - // assemblies, preventing CS0436 conflicts when multiple projects reference this generator. - ctx.AddEmbeddedAttributeDefinition(); - - // Marker-attribute templates are emitted through a CodeWriter (see - // MarkerAttributeTemplateEmitter) rather than loaded from embedded resources. - MarkerAttributeTemplateEmitter.EmitAll(ctx); - }); + context.RegisterPostInitializationOutput(GeneratedTypesEmitter.EmitAll); // The generation context carries the framework logging sink, settings and the // compilation-level capabilities (nullable annotations, IMeterFactory availability). @@ -63,9 +50,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var activityProvider = IncrementalPipeline .ForAttributeWithMetadataName( context, - TemplateLibrary.Activities.ActivitySourceAttribute.TypeInfo, + TypeLibrary.Activities.ActivitySourceAttribute, transform: static (ctx, cancellationToken) => - PipelineHelpers.BuildActivityTransform(ctx, null, cancellationToken), + PipelineHelpers.BuildActivityTransform(ctx, cancellationToken), predicate: static (node, token) => PipelineHelpers.HasActivityTargetAttribute(node, token), trackingName: $"{nameof(TelemetrySourceGenerator)}_Activities" ) @@ -74,9 +61,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var meterProvider = IncrementalPipeline .ForAttributeWithMetadataName( context, - TemplateLibrary.Metrics.MeterAttribute.TypeInfo, + TypeLibrary.Metrics.MeterAttribute, transform: static (ctx, cancellationToken) => - PipelineHelpers.BuildMeterTransform(ctx, null, cancellationToken), + PipelineHelpers.BuildMeterTransform(ctx, cancellationToken), predicate: static (node, token) => PipelineHelpers.HasMeterTargetAttribute(node, token), trackingName: $"{nameof(TelemetrySourceGenerator)}_Meters" ) @@ -101,8 +88,8 @@ CancellationToken token // IMeterFactory is .NET 8+ only — never available on .NET Framework 4.8. var parseOptions = compilation.SyntaxTrees.FirstOrDefault()?.Options as CSharpParseOptions; return new( - SupportsNullableAnnotations: parseOptions is null - || parseOptions.LanguageVersion >= LanguageVersion.CSharp8, + //SupportsNullableAnnotations: parseOptions is null + //|| parseOptions.LanguageVersion >= LanguageVersion.CSharp8, SupportsIMeterFactory: parseOptions is null || !parseOptions.PreprocessorSymbolNames.Contains("NET48_OR_GREATER") ); diff --git a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Activities.cs b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Activities.cs index fb0c0f5c..f5c7a1f6 100644 --- a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Activities.cs +++ b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Activities.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis; using Purview.Telemetry.SourceGenerator.Helpers; -using Purview.Telemetry.SourceGenerator.Records; namespace Purview.Telemetry.SourceGenerator; @@ -9,164 +8,224 @@ partial class DiagnosticLibrary // Start at 3000 public static class Activities { - public static readonly TelemetryDiagnosticDescriptor BaggageParameterShouldBeString = new( - Id: "TSG3000", - Title: "Baggage parameter types only accept strings", - Description: "Baggage parameter types only accept strings, be aware this parameter will have ToString() called.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor NoActivitySourceSpecified = new( - Id: "TSG3001", - Title: "No activity source specified", - Description: $"An activity source helps to identify your application and it's telemetry. Defaulting to '{PropertyLibrary.Activities.DefaultActivitySourceName}'.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor InvalidReturnType = new( - Id: "TSG3002", - Title: "Invalid return type", - Description: $"An activity or event must return either void or an {TypeLibrary.Activities.SystemDiagnostics.Activity.Name}.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor DuplicateParameterTypes = new( - Id: "TSG3003", - Title: "Duplicate reserved parameters defined", - Description: "{0} are all the same type of parameter ({1}), a maximum or one is allowed. Explicitly define them as either a Tag or Baggage.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor ActivityParameterNotAllowed = new( - Id: "TSG3004", - Title: "Activity parameter is not valid", - Description: "The {0} parameter is not allowed when defining an activity, only an event.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor TimestampParameterNotAllowed = new( - Id: "TSG3005", - Title: "Timestamp parameter is not valid", - Description: "The {0} parameter is not allowed when defining an activity, only an event. You can specify this as a Tag or as Baggage to stop the inference.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor StartTimeParameterNotAllowed = new( - Id: "TSG3006", - Title: "Start time parameter is not valid on Create activity or Event method", - Description: "The {0} parameter is not allowed when defining an activity create or activity event method, only when starting an activity.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor ParentContextOrIdParameterNotAllowed = new( - Id: "TSG3007", - Title: "Parent context or Parent Id parameter is not valid on event", - Description: "The {0} parameter is not allowed when defining an activity event, only on the activity start/ create method.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor LinksParameterNotAllowed = new( - Id: "TSG3008", - Title: "Activity links parameters are not valid on events or context methods", - Description: "The {0} parameter is not allowed when defining an activity event or context, only on the activity start/ create method.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor TagsParameterNotAllowed = new( - Id: "TSG3009", - Title: "Activity tags parameter are not valid on context methods", - Description: "The {0} parameter is not allowed when defining an activity context, only on the activity start/ create methods or events.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor EscapedParameterInvalidType = new( - Id: "TSG3010", - Title: "Escaped parameters must be a boolean", - Description: "Only boolean parameter types are valid for the escape parameter.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor EscapedParameterIsOnlyValidOnEvent = new( - Id: "TSG3011", - Title: "Escaped parameters are only valid on Events, not Activity or Context methods", - Description: "The parameters {0} is not valid on Activity or Context methods, only on Events.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor NoActivityMethodsDefined = new( - Id: "TSG3012", - Title: "There are no Activity methods defined, assumed use of Activity.Current", - Description: "As Event and/ or Context methods are defined, it's best practice to create a specific Activity otherwise the Activity will belong to another operation.", - Severity: DiagnosticSeverity.Info, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor DoesNotReturnActivity = new( - Id: "TSG3013", - Title: "Should return the created Activity", - Description: "It's best practice to return the created Activity so you can dispose of it, and use it for Event or Context methods.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor DoesNotAcceptActivityParameter = new( - Id: "TSG3014", - Title: "Should accept an Activity to apply the Event/ Tags/ Baggage too", - Description: "It's best practice to accept an Activity parameter to ensure the Event, Tags and/ or Baggage are applied to the Activity you intended.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor ActivityShouldBeTheFirstParameter = new( - Id: "TSG3015", - Title: "Activity should be the first parameter", - Description: "For readability, ensure the Activity parameter is the first one defined.", - Severity: DiagnosticSeverity.Info, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor StatusDescriptionMustBeString = new( - Id: "TSG3016", - Title: "Status description parameter should be a string", - Description: "Status descriptions can only be of type string.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor StatusDescriptionParameterInvalidType = new( - Id: "TSG3017", - Title: "Status Description parameters are only valid on Events, not Activity or Context methods", - Description: "The parameters {0} is not valid on Activity or Context methods, only on Events.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor ActivityReturnTypeShouldBeNullable = new( - Id: "TSG3022", - Title: "Activity return type should be nullable", - Description: "Activity can be null when no listeners are active. Consider using 'Activity?' as the return type to make this explicit.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Activity.Usage - ); - - public static readonly TelemetryDiagnosticDescriptor ExceptionEventNotStandardName = new( - Id: "TSG3021", - Title: "Exception event does not use OpenTelemetry standard name", - Description: "Event '{0}' records an exception but does not use the OpenTelemetry standard name 'exception'. Consider using [Event(Name = \"exception\")] for better observability.", - Severity: DiagnosticSeverity.Info, - Category: Categories.Activity.Usage + public static readonly DiagnosticInfo BaggageParameterShouldBeString = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3000", + title: "Baggage parameter types only accept strings", + messageFormat: "Baggage parameter types only accept strings, be aware this parameter will have ToString() called.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo NoActivitySourceSpecified = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3001", + title: "No activity source specified", + messageFormat: $"An activity source helps to identify your application and it's telemetry. Defaulting to '{PropertyLibrary.Activities.DefaultActivitySourceName}'.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo InvalidReturnType = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3002", + title: "Invalid return type", + messageFormat: $"An activity or event must return either void or an {TypeLibrary.Activities.SystemDiagnostics.Activity.Name}.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo DuplicateParameterTypes = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3003", + title: "Duplicate reserved parameters defined", + messageFormat: "{0} are all the same type of parameter ({1}), a maximum or one is allowed. Explicitly define them as either a Tag or Baggage.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo ActivityParameterNotAllowed = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3004", + title: "Activity parameter is not valid", + messageFormat: "The {0} parameter is not allowed when defining an activity, only an event.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo TimestampParameterNotAllowed = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3005", + title: "Timestamp parameter is not valid", + messageFormat: "The {0} parameter is not allowed when defining an activity, only an event. You can specify this as a Tag or as Baggage to stop the inference.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo StartTimeParameterNotAllowed = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3006", + title: "Start time parameter is not valid on Create activity or Event method", + messageFormat: "The {0} parameter is not allowed when defining an activity create or activity event method, only when starting an activity.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo ParentContextOrIdParameterNotAllowed = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3007", + title: "Parent context or Parent Id parameter is not valid on event", + messageFormat: "The {0} parameter is not allowed when defining an activity event, only on the activity start/ create method.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo LinksParameterNotAllowed = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3008", + title: "Activity links parameters are not valid on events or context methods", + messageFormat: "The {0} parameter is not allowed when defining an activity event or context, only on the activity start/ create method.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo TagsParameterNotAllowed = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3009", + title: "Activity tags parameter are not valid on context methods", + messageFormat: "The {0} parameter is not allowed when defining an activity context, only on the activity start/ create methods or events.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo EscapedParameterInvalidType = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3010", + title: "Escaped parameters must be a boolean", + messageFormat: "Only boolean parameter types are valid for the escape parameter.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo EscapedParameterIsOnlyValidOnEvent = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3011", + title: "Escaped parameters are only valid on Events, not Activity or Context methods", + messageFormat: "The parameters {0} is not valid on Activity or Context methods, only on Events.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo NoActivityMethodsDefined = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3012", + title: "There are no Activity methods defined, assumed use of Activity.Current", + messageFormat: "As Event and/ or Context methods are defined, it's best practice to create a specific Activity otherwise the Activity will belong to another operation.", + defaultSeverity: DiagnosticSeverity.Info, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo DoesNotReturnActivity = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3013", + title: "Should return the created Activity", + messageFormat: "It's best practice to return the created Activity so you can dispose of it, and use it for Event or Context methods.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo DoesNotAcceptActivityParameter = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3014", + title: "Should accept an Activity to apply the Event/ Tags/ Baggage too", + messageFormat: "It's best practice to accept an Activity parameter to ensure the Event, Tags and/ or Baggage are applied to the Activity you intended.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo ActivityShouldBeTheFirstParameter = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3015", + title: "Activity should be the first parameter", + messageFormat: "For readability, ensure the Activity parameter is the first one defined.", + defaultSeverity: DiagnosticSeverity.Info, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo StatusDescriptionMustBeString = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3016", + title: "Status description parameter should be a string", + messageFormat: "Status descriptions can only be of type string.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo StatusDescriptionParameterInvalidType = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3017", + title: "Status Description parameters are only valid on Events, not Activity or Context methods", + messageFormat: "The parameters {0} is not valid on Activity or Context methods, only on Events.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo ActivityReturnTypeShouldBeNullable = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3022", + title: "Activity return type should be nullable", + messageFormat: "Activity can be null when no listeners are active. Consider using 'Activity?' as the return type to make this explicit.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) + ); + + public static readonly DiagnosticInfo ExceptionEventNotStandardName = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG3021", + title: "Exception event does not use OpenTelemetry standard name", + messageFormat: "Event '{0}' records an exception but does not use the OpenTelemetry standard name 'exception'. Consider using [Event(Name = \"exception\")] for better observability.", + defaultSeverity: DiagnosticSeverity.Info, + category: Categories.Activity.Usage, + isEnabledByDefault: true + ) ); } } diff --git a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.General.cs b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.General.cs index 87863333..9d21ad99 100644 --- a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.General.cs +++ b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.General.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis; using Purview.Telemetry.SourceGenerator.Helpers; -using Purview.Telemetry.SourceGenerator.Records; namespace Purview.Telemetry.SourceGenerator; @@ -8,102 +7,135 @@ partial class DiagnosticLibrary { public static class General { - public static readonly TelemetryDiagnosticDescriptor FatalExecutionDuringExecution = new( - Id: "TSG1000", - Title: "Fatal execution error occurred", - Description: "Failed to execute the generation stage: {0}", - Severity: DiagnosticSeverity.Error, - Category: Categories.Usage + public static readonly DiagnosticInfo FatalExecutionDuringExecution = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1000", + title: "Fatal execution error occurred", + messageFormat: "Failed to execute the generation stage: {0}", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor InferenceNotSupportedWithMultiTargeting = new( - Id: "TSG1001", - Title: "Inferring generation targets is not supported when using multi-target generation", - Description: "When using multiple generation targets - Activities, Logs or Metrics, each method must be either excluded or have an explicit generation target: " - + $"{TemplateLibrary.Activities.ActivityAttribute.Name}, {TemplateLibrary.Activities.EventAttribute.Name}, {TemplateLibrary.Activities.ContextAttribute.Name}, {TemplateLibrary.Logging.LogAttribute.Name}, " - + $"{TemplateLibrary.Logging.WarningAttribute.Name}, " - + $"{TemplateLibrary.Metrics.CounterAttribute.Name}, {TemplateLibrary.Metrics.HistogramAttribute.Name}, {TemplateLibrary.Metrics.UpDownCounterAttribute.Name}, " - + $"{TemplateLibrary.Metrics.ObservableCounterAttribute.Name}, {TemplateLibrary.Metrics.ObservableGaugeAttribute.Name} or {TemplateLibrary.Metrics.ObservableUpDownCounterAttribute.Name}.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Usage + public static readonly DiagnosticInfo InferenceNotSupportedWithMultiTargeting = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1001", + title: "Inferring generation targets is not supported when using multi-target generation", + messageFormat: "When using multiple generation targets - Activities, Logs or Metrics, each method must be either excluded or have an explicit generation target: " + + $"{TypeLibrary.Activities.ActivityAttribute.Name}, {TypeLibrary.Activities.EventAttribute.Name}, {TypeLibrary.Activities.ContextAttribute.Name}, {TypeLibrary.Logging.LogAttribute.Name}, " + + $"{TypeLibrary.Logging.WarningAttribute.Name}, " + + $"{TypeLibrary.Metrics.CounterAttribute.Name}, {TypeLibrary.Metrics.HistogramAttribute.Name}, {TypeLibrary.Metrics.UpDownCounterAttribute.Name}, " + + $"{TypeLibrary.Metrics.ObservableCounterAttribute.Name}, {TypeLibrary.Metrics.ObservableGaugeAttribute.Name} or {TypeLibrary.Metrics.ObservableUpDownCounterAttribute.Name}.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor MultiGenerationTargetsNotSupported = new( - Id: "TSG1002", - Title: "Multiple attributes from the same target family are not supported", - Description: "Only a single attribute per target family (Activities, Logs, or Metrics) is allowed on each method. " - + "For Activities: use one of ActivityAttribute, EventAttribute, or ContextAttribute. " - + "For Logging: use one of LogAttribute or a semantic level attribute (TraceAttribute, DebugAttribute, etc.). " - + "For Metrics: use one instrument attribute (CounterAttribute, HistogramAttribute, etc.).", - Severity: DiagnosticSeverity.Error, - Category: Categories.Usage + public static readonly DiagnosticInfo MultiGenerationTargetsNotSupported = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1002", + title: "Multiple attributes from the same target family are not supported", + messageFormat: "Only a single attribute per target family (Activities, Logs, or Metrics) is allowed on each method. " + + "For Activities: use one of ActivityAttribute, EventAttribute, or ContextAttribute. " + + "For Logging: use one of LogAttribute or a semantic level attribute (TraceAttribute, DebugAttribute, etc.). " + + "For Metrics: use one instrument attribute (CounterAttribute, HistogramAttribute, etc.).", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor DuplicateMethodNamesAreNotSupported = new( - Id: "TSG1003", - Title: "Duplicate method names are not supported", - Description: "Two or more methods named '{0}' are defined. Keep method names unique as they're used to generate other members on the implementation class.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Usage + public static readonly DiagnosticInfo DuplicateMethodNamesAreNotSupported = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1003", + title: "Duplicate method names are not supported", + messageFormat: "Two or more methods named '{0}' are defined. Keep method names unique as they're used to generate other members on the implementation class.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor GenericInterfacesNotSupported = new( - Id: "TSG1004", - Title: "Generic interfaces are not supported", - Description: "Remove the generic type(s) from the interface, this type of generation is not supported.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Usage + public static readonly DiagnosticInfo GenericInterfacesNotSupported = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1004", + title: "Generic interfaces are not supported", + messageFormat: "Remove the generic type(s) from the interface, this type of generation is not supported.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor GenericMethodsNotSupported = new( - Id: "TSG1005", - Title: "Generic methods are not supported", - Description: "Remove the generic type(s) from the method, this type of generation is not supported.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Usage + public static readonly DiagnosticInfo GenericMethodsNotSupported = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1005", + title: "Generic methods are not supported", + messageFormat: "Remove the generic type(s) from the method, this type of generation is not supported.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor ExcludeTargetsTargetNotPresent = new( - Id: "TSG1006", - Title: "ExcludeTargets references a target not present on this method", - Description: "The parameter has [ExcludeTargets] excluding '{0}', but this method does not have any attributes for that target family.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Usage + public static readonly DiagnosticInfo ExcludeTargetsTargetNotPresent = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1006", + title: "ExcludeTargets references a target not present on this method", + messageFormat: "The parameter has [ExcludeTargets] excluding '{0}', but this method does not have any attributes for that target family.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor ExcludeTargetsResultsInEmptyParameterSet = new( - Id: "TSG1007", - Title: "ExcludeTargets results in an empty or invalid parameter set for a target", - Description: "Excluding parameters results in an invalid configuration for the '{0}' target: {1}", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Usage + public static readonly DiagnosticInfo ExcludeTargetsResultsInEmptyParameterSet = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1007", + title: "ExcludeTargets results in an empty or invalid parameter set for a target", + messageFormat: "Excluding parameters results in an invalid configuration for the '{0}' target: {1}", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor ActivityParameterWithoutActivityTarget = new( - Id: "TSG1008", - Title: "Activity parameter has no Activity target", - Description: "Parameter '{0}' of type Activity is present, but this method has no Activity attribute ([Activity], [Event], or [Context]). The parameter will be ignored.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Usage + public static readonly DiagnosticInfo ActivityParameterWithoutActivityTarget = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1008", + title: "Activity parameter has no Activity target", + messageFormat: "Parameter '{0}' of type Activity is present, but this method has no Activity attribute ([Activity], [Event], or [Context]). The parameter will be ignored.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor MethodTargetNotRegisteredOnInterface = new( - Id: "TSG1010", - Title: "Method target not registered on interface", - Description: "Method has attribute(s) for a generation target that is not registered on the interface. " - + $"Add the corresponding source attribute ([{TemplateLibrary.Activities.ActivitySourceAttribute.Name}], [{TemplateLibrary.Logging.LoggerAttribute.Name}], or [{TemplateLibrary.Metrics.MeterAttribute.Name}]) " - + "to the interface to enable generation for the target(s) used by this method.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Usage + public static readonly DiagnosticInfo MethodTargetNotRegisteredOnInterface = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1010", + title: "Method target not registered on interface", + messageFormat: "Method has attribute(s) for a generation target that is not registered on the interface. " + + $"Add the corresponding source attribute ([{TypeLibrary.Activities.ActivitySourceAttribute.Name}], [{TypeLibrary.Logging.LoggerAttribute.Name}], or [{TypeLibrary.Metrics.MeterAttribute.Name}]) " + + "to the interface to enable generation for the target(s) used by this method.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor UnsupportedTargetFramework = new( - Id: "TSG1011", - Title: "Unsupported target framework", - Description: "Purview Telemetry Source Generator requires .NET 8.0 or higher, or .NET Framework 4.8 or higher. " - + "The current target framework is not supported. " - + "If this is incorrect, define PURVIEW_TELEMETRY_NON_NULLABLE to suppress this error and opt out of nullable reference type annotations.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Usage + public static readonly DiagnosticInfo UnsupportedTargetFramework = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG1011", + title: "Unsupported target framework", + messageFormat: "Purview Telemetry Source Generator requires .NET 8.0 or higher, or .NET Framework 4.8 or higher. " + + "The current target framework is not supported. " + + "If this is incorrect, define PURVIEW_TELEMETRY_NON_NULLABLE to suppress this error and opt out of nullable reference type annotations.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Usage, + isEnabledByDefault: true + ) ); } } diff --git a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Logging.cs b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Logging.cs index d75b3bf4..e51ee5fe 100644 --- a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Logging.cs +++ b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Logging.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis; using Purview.Telemetry.SourceGenerator.Helpers; -using Purview.Telemetry.SourceGenerator.Records; namespace Purview.Telemetry.SourceGenerator; @@ -9,84 +8,114 @@ partial class DiagnosticLibrary // Start at 2000 public static class Logging { - public static readonly TelemetryDiagnosticDescriptor MultipleExceptionsDefined = new( - Id: "TSG2000", - Title: "Too many exception parameters", - Description: "Only a single exceptions parameter is permitted.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo MultipleExceptionsDefined = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2000", + title: "Too many exception parameters", + messageFormat: "Only a single exceptions parameter is permitted.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor MaximumLogEntryParametersExceeded = new( - Id: "TSG2001", - Title: "More than 6 parameters", - Description: $"The maximum number of parameters (excluding optional Exception) is {PropertyLibrary.Logging.MaxNonExceptionParameters}", - Severity: DiagnosticSeverity.Error, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo MaximumLogEntryParametersExceeded = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2001", + title: "More than 6 parameters", + messageFormat: $"The maximum number of parameters (excluding optional Exception) is {PropertyLibrary.Logging.MaxNonExceptionParameters}", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor InferringErrorLogLevel = new( - Id: "TSG2002", - Title: "Inferring error log level", - Description: "Because an exception parameter was defined and no log level was defined the level was inferred to be Error. Consider explicitly defining the required level.", - Severity: DiagnosticSeverity.Info, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo InferringErrorLogLevel = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2002", + title: "Inferring error log level", + messageFormat: "Because an exception parameter was defined and no log level was defined the level was inferred to be Error. Consider explicitly defining the required level.", + defaultSeverity: DiagnosticSeverity.Info, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor MSLoggingNotReferenced = new( - Id: "TSG2003", - Title: "Could not find a reference to Microsoft.Extensions.Logging.ILogger, skipping log generation", - Description: "No reference was found for the ILogger type, no log generation is possible so no logging attributes will be added. Add a reference to the appropriate NuGet package, such as Microsoft.Extensions.Logging.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo MSLoggingNotReferenced = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2003", + title: "Could not find a reference to Microsoft.Extensions.Logging.ILogger, skipping log generation", + messageFormat: "No reference was found for the ILogger type, no log generation is possible so no logging attributes will be added. Add a reference to the appropriate NuGet package, such as Microsoft.Extensions.Logging.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor MixedOrdinalAndNamedProperties = new( - Id: "TSG2004", - Title: "Cannot mix ordinal and named property placeholders", - Description: "The message template for log method '{0}' mixes ordinal and named property placeholders which is not supported.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo MixedOrdinalAndNamedProperties = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2004", + title: "Cannot mix ordinal and named property placeholders", + messageFormat: "The message template for log method '{0}' mixes ordinal and named property placeholders which is not supported.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor OrdinalsExceedParameters = new( - Id: "TSG2005", - Title: "Ordinal values exceed parameter count", - Description: "The maximum ordinal value for log method '{0}' exceeds the number of provided parameters.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo OrdinalsExceedParameters = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2005", + title: "Ordinal values exceed parameter count", + messageFormat: "The maximum ordinal value for log method '{0}' exceeds the number of provided parameters.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor ExpandEnumerableAndLogPropertiesNotSupported = new( - Id: "TSG2006", - Title: "Using LogPropertiesAttribute and ExpandEnumerableAttribute on the same parameter is not supported", - Description: "Expanding an array/ IEnumerable, and the expanding the complex type of the items in the array are not supported.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo ExpandEnumerableAndLogPropertiesNotSupported = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2006", + title: "Using LogPropertiesAttribute and ExpandEnumerableAttribute on the same parameter is not supported", + messageFormat: "Expanding an array/ IEnumerable, and the expanding the complex type of the items in the array are not supported.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor ScopedMethodShouldNotHaveLevel = new( - Id: "TSG2007", - Title: "A scoped log shouldn't have a LogLevel, this will be ignored.", - Description: "Scoped log entries do not support having a log level set.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo ScopedMethodShouldNotHaveLevel = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2007", + title: "A scoped log shouldn't have a LogLevel, this will be ignored.", + messageFormat: "Scoped log entries do not support having a log level set.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor UnboundedIEnumerableMaxCount = new( - Id: "TSG2008", - Title: "Unbounded enumeration possible", - Description: $"The limit on unbounded enumeration is higher than the recommended default ({PropertyLibrary.Logging.UnboundedIEnumerableMaxCountBeforeDiagnostic}). This may cause performance issues, make sure you understand the consequences and test thoroughly.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Logging.Performance + public static readonly DiagnosticInfo UnboundedIEnumerableMaxCount = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2008", + title: "Unbounded enumeration possible", + messageFormat: $"The limit on unbounded enumeration is higher than the recommended default ({PropertyLibrary.Logging.UnboundedIEnumerableMaxCountBeforeDiagnostic}). This may cause performance issues, make sure you understand the consequences and test thoroughly.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Logging.Performance, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor LogMustReturnVoidOrAsync = new( - Id: "TSG2021", - Title: "Log method must return void or IDisposable", - Description: "Logging methods can only return void (non-scoped) or IDisposable (scoped). Other return types like string, int, bool, Activity, Task, or ValueTask are not supported.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Logging.Usage + public static readonly DiagnosticInfo LogMustReturnVoidOrAsync = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG2021", + title: "Log method must return void or IDisposable", + messageFormat: "Logging methods can only return void (non-scoped) or IDisposable (scoped). Other return types like string, int, bool, Activity, Task, or ValueTask are not supported.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Logging.Usage, + isEnabledByDefault: true + ) ); } } diff --git a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Metrics.cs b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Metrics.cs index 1a73e390..bcfe21d9 100644 --- a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Metrics.cs +++ b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.Metrics.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis; using Purview.Telemetry.SourceGenerator.Helpers; -using Purview.Telemetry.SourceGenerator.Records; namespace Purview.Telemetry.SourceGenerator; @@ -9,84 +8,114 @@ partial class DiagnosticLibrary // Starts at 4000 public static class Metrics { - public static readonly TelemetryDiagnosticDescriptor NoInstrumentDefined = new( - Id: "TSG4000", - Title: "No instrument defined", - Description: "Either exclude this method, or define an instrument.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo NoInstrumentDefined = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4000", + title: "No instrument defined", + messageFormat: "Either exclude this method, or define an instrument.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor DoesNotReturnVoid = new( - Id: "TSG4001", - Title: "Must return void or bool", - Description: "Instrument methods can only return void or boolean.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo DoesNotReturnVoid = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4001", + title: "Must return void or bool", + messageFormat: "Instrument methods can only return void or boolean.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor AutoIncrementCountAndMeasurementParam = new( - Id: "TSG4002", - Title: "Auto increment counter and measurement defined", - Description: "Auto increment counter and a measurement parameter are defined, either remove the parameter/ attribute or change to a normal counter.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo AutoIncrementCountAndMeasurementParam = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4002", + title: "Auto increment counter and measurement defined", + messageFormat: "Auto increment counter and a measurement parameter are defined, either remove the parameter/ attribute or change to a normal counter.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor MoreThanOneMeasurementValueDefined = new( - Id: "TSG4003", - Title: "Multiple measurement values defined", - Description: "More than one measurement parameters are defined.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo MoreThanOneMeasurementValueDefined = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4003", + title: "Multiple measurement values defined", + messageFormat: "More than one measurement parameters are defined.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor NoMeasurementValueDefined = new( - Id: "TSG4004", - Title: "No measurement value defined", - Description: "Either define a measurement parameter, or provide a supported type parameter that is not a tag to enable inferring.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo NoMeasurementValueDefined = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4004", + title: "No measurement value defined", + messageFormat: "Either define a measurement parameter, or provide a supported type parameter that is not a tag to enable inferring.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor ObservableRequiredFunc = new( - Id: "TSG4005", - Title: "Observable instrument requires Func", - Description: "Observable instruments require a Func where T is a supported instrument result type.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo ObservableRequiredFunc = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4005", + title: "Observable instrument requires Func", + messageFormat: "Observable instruments require a Func where T is a supported instrument result type.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor InvalidMeasurementType = new( - Id: "TSG4006", - Title: "Invalid measurement type", - Description: $"Invalid measurement type used, valid types are {string.Join(", ", PropertyLibrary.Metrics.ValidMeasurementKeywordTypes)}, Measurement or IEnumerable>.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo InvalidMeasurementType = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4006", + title: "Invalid measurement type", + messageFormat: $"Invalid measurement type used, valid types are {string.Join(", ", PropertyLibrary.Metrics.ValidMeasurementKeywordTypes)}, Measurement or IEnumerable>.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor ObservableCannotReturnBool = new( - Id: "TSG4007", - Title: "Observable metrics cannot return bool", - Description: "Observable metrics can only return void or Activity? (when combined with Activity attribute). Boolean returns are not supported for observables.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo ObservableCannotReturnBool = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4007", + title: "Observable metrics cannot return bool", + messageFormat: "Observable metrics can only return void or Activity? (when combined with Activity attribute). Boolean returns are not supported for observables.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor AutoCounterMustReturnVoid = new( - Id: "TSG4008", - Title: "AutoCounter must return void", - Description: "AutoCounter methods must return void. Boolean or other return types are not supported.", - Severity: DiagnosticSeverity.Error, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo AutoCounterMustReturnVoid = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4008", + title: "AutoCounter must return void", + messageFormat: "AutoCounter methods must return void. Boolean or other return types are not supported.", + defaultSeverity: DiagnosticSeverity.Error, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); - public static readonly TelemetryDiagnosticDescriptor InstrumentNameMatchesType = new( - Id: "TSG4009", - Title: "Instrument name matches the instrument type name", - Description: "Instrument name '{0}' matches the instrument type name. Use a name that describes what is being measured, not the instrument type.", - Severity: DiagnosticSeverity.Warning, - Category: Categories.Metrics.Usage + public static readonly DiagnosticInfo InstrumentNameMatchesType = DiagnosticInfo.Create( + new DiagnosticDescriptor( + id: "TSG4009", + title: "Instrument name matches the instrument type name", + messageFormat: "Instrument name '{0}' matches the instrument type name. Use a name that describes what is being measured, not the instrument type.", + defaultSeverity: DiagnosticSeverity.Warning, + category: Categories.Metrics.Usage, + isEnabledByDefault: true + ) ); } } diff --git a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.cs b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.cs index dd683869..71776706 100644 --- a/src/src/SourceGenerator/Helpers/DiagnosticLibrary.cs +++ b/src/src/SourceGenerator/Helpers/DiagnosticLibrary.cs @@ -1,5 +1,5 @@ +using System.Collections.Immutable; using Microsoft.CodeAnalysis; -using Purview.Telemetry.SourceGenerator.Records; namespace Purview.Telemetry.SourceGenerator; @@ -26,49 +26,37 @@ public static class Metrics } } - public static void Report( - Action report, - TelemetryDiagnosticDescriptor telemetryDiagnostic, - params object?[] args - ) => Report(report, telemetryDiagnostic, locations: null, args); + public static void Report(Action report, DiagnosticInfo telemetryDiagnostic, params object?[] args) => + Report(report, telemetryDiagnostic, locations: null, args); public static void Report( Action report, - TelemetryDiagnosticDescriptor telemetryDiagnostic, + DiagnosticInfo telemetryDiagnostic, Location? location, params object?[] args ) => Report(report, telemetryDiagnostic, location == null ? null : [location], args); public static void Report( Action report, - TelemetryDiagnosticDescriptor telemetryDiagnostic, + DiagnosticInfo telemetryDiagnostic, IEnumerable? locations, params object?[] args ) => Report(report, telemetryDiagnostic, locations?.ToArray(), args); public static void Report( Action report, - TelemetryDiagnosticDescriptor telemetryDiagnostic, + DiagnosticInfo telemetryDiagnostic, Location[]? locations, params object?[] args ) { var location = locations?.Length > 0 ? locations[0] : null; - var additionalLocations = locations?.Length > 1 ? locations.AsSpan().Slice(1) : null; + var additionalLocations = + locations?.Length > 1 ? locations.AsSpan().Slice(1).ToImmutableArray() : (ImmutableArray?)null; - var diagnostic = Diagnostic.Create( - new( - id: telemetryDiagnostic.Id, - title: telemetryDiagnostic.Title, - messageFormat: telemetryDiagnostic.Description, - category: telemetryDiagnostic.Category, - defaultSeverity: telemetryDiagnostic.Severity, - isEnabledByDefault: telemetryDiagnostic.EnabledByDefault - ), - location, - additionalLocations.ToArray(), - args - ); + var diagnostic = DiagnosticInfo + .Create(telemetryDiagnostic.Descriptor, location, additionalLocations, args!) + .ToDiagnostic(); report(diagnostic); } diff --git a/src/src/SourceGenerator/Helpers/InstrumentMethodModelBuilder.cs b/src/src/SourceGenerator/Helpers/InstrumentMethodModelBuilder.cs index ff5dd27c..3db44374 100644 --- a/src/src/SourceGenerator/Helpers/InstrumentMethodModelBuilder.cs +++ b/src/src/SourceGenerator/Helpers/InstrumentMethodModelBuilder.cs @@ -16,7 +16,6 @@ public static ImmutableArray BuildInstrumentationMethods( TelemetryGenerationAttributeData telemetryGeneration, string meterName, INamedTypeSymbol interfaceSymbol, - ISourceGenLogger? logger, CancellationToken token ) { @@ -35,18 +34,13 @@ CancellationToken token { token.ThrowIfCancellationRequested(); - if (Utilities.ContainsAttribute(method, TemplateLibrary.Shared.ExcludeAttribute, token)) - { - logger?.Debug($"Skipping {interfaceSymbol.Name}.{method.Name}, explicitly excluded."); + if (TypeHelpers.HasAttribute(method, TypeLibrary.TelemetryShared.ExcludeAttribute)) continue; - } if (method.Arity > 0) continue; - logger?.Debug($"Found possible instrument method {interfaceSymbol.Name}.{method.Name}."); - - var instrumentAttribute = SharedHelpers.GetInstrumentAttribute(method, logger, token); + var instrumentAttribute = SharedHelpers.GetInstrumentAttribute(method, token); var validAutoCounter = instrumentAttribute?.InstrumentType is InstrumentTypes.Counter && instrumentAttribute.IsAutoIncrement; @@ -55,7 +49,6 @@ CancellationToken token lowercaseTagKeys, validAutoCounter, namingConvention, - logger, token ); var measurementParameters = parameters @@ -87,12 +80,7 @@ CancellationToken token GenerationType.Metrics ); - if (!targetGenerationState.IsValid) - LogTargetState(logger, interfaceSymbol.Name, method.Name, targetGenerationState); - else if (instrumentAttribute == null) - logger?.Warning("Missing instrument attribute."); - - var instrumentMeasurementType = measurementParameter?.InstrumentType ?? TypeLibrary.System.Int32; + var instrumentMeasurementType = measurementParameter?.InstrumentType ?? PurviewTypeLibrary.System.Int32; methodTargets.Add( new( @@ -164,32 +152,11 @@ string meterName return (instrumentName, prefix); } - static void LogTargetState( - ISourceGenLogger? logger, - string interfaceName, - string methodName, - TargetGeneration targetGenerationState - ) - { - if (targetGenerationState.IsValid) - return; - - if (targetGenerationState.RaiseMultiGenerationTargetsNotSupported) - logger?.Debug($"Identified {interfaceName}.{methodName} as problematic as it has another target types."); - else if (targetGenerationState.RaiseInferenceNotSupportedWithMultiTargeting) - logger?.Debug($"Identified {interfaceName}.{methodName} as problematic as it is inferred."); - else if (targetGenerationState.RaiseMissingInterfaceSource) - logger?.Debug( - $"Identified {interfaceName}.{methodName} as problematic as the interface is missing source attribute(s) for the method's target(s)." - ); - } - static ImmutableArray GetInstrumentParameters( IMethodSymbol method, bool lowercaseTagKeys, bool isAutoCounter, int namingConvention, - ISourceGenLogger? logger, CancellationToken token ) { @@ -207,11 +174,10 @@ CancellationToken token || paramType.Identity.Equals(TypeLibrary.Activities.SystemDiagnostics.ActivityLinkArray) ) { - logger?.Debug($"Skipping Activity-related parameter '{parameter.Name}' from metrics."); continue; } - var tagAttribute = GetDestination(parameter, logger, token, out var destination); + var tagAttribute = GetDestination(parameter, token, out var destination); var isFuncType = false; var isIEnumerableType = false; @@ -222,16 +188,13 @@ CancellationToken token if (destination != InstrumentParameterDestination.Tag) { (isFuncType, isIEnumerableType, isMeasurementType, isValidInstrumentType, instrumentType) = - TryResolveMeasurementType(parameter.Type, isAutoCounter, logger); + TryResolveMeasurementType(parameter.Type, isAutoCounter); if (instrumentType != null) destination = InstrumentParameterDestination.Measurement; } if (destination == InstrumentParameterDestination.Unknown) - { - logger?.Debug($"Unable to match parameter {parameter.Name}, inferring tag."); destination = InstrumentParameterDestination.Tag; - } var parameterName = parameter.Name; var generatedName = PipelineHelpers.GenerateParameterName( @@ -266,25 +229,27 @@ CancellationToken token static TagOrBaggageAttributeRecord? GetDestination( IParameterSymbol parameter, - ISourceGenLogger? logger, CancellationToken token, out InstrumentParameterDestination destination ) { destination = InstrumentParameterDestination.Unknown; - if (Utilities.TryContainsAttribute(parameter, TemplateLibrary.Shared.TagAttribute, token, out var attribute)) + if ( + Utilities.TryContainsAttribute( + parameter, + TypeLibrary.TelemetryShared.TagAttribute, + token, + out var attribute + ) + ) { - logger?.Debug($"Found explicit tag: {parameter.Name}."); destination = InstrumentParameterDestination.Tag; return SharedHelpers.GetTagOrBaggageAttribute(attribute!, token); } - if (Utilities.ContainsAttribute(parameter, TemplateLibrary.Metrics.InstrumentMeasurementAttribute, token)) - { - logger?.Debug($"Found explicit instrument measurement: {parameter.Name}."); + if (TypeHelpers.HasAttribute(parameter, TypeLibrary.Metrics.InstrumentMeasurementAttribute)) destination = InstrumentParameterDestination.Measurement; - } return null; } @@ -295,7 +260,7 @@ out InstrumentParameterDestination destination bool IsMeasurement, bool IsValidInstrumentType, TypeReference? InstrumentType - ) TryResolveMeasurementType(ITypeSymbol parameterType, bool isAutoCounter, ISourceGenLogger? logger) + ) TryResolveMeasurementType(ITypeSymbol parameterType, bool isAutoCounter) { if (parameterType is not INamedTypeSymbol namedParameterType) return (false, false, false, false, null); @@ -308,7 +273,6 @@ out InstrumentParameterDestination destination if (isValid && !isAutoCounter) { var typeRef = TypeReference.Create(namedParameterType); - logger?.Debug($"Found valid instrument type: {typeRef}"); return (false, false, false, true, typeRef); } @@ -336,7 +300,6 @@ enumerableArg is INamedTypeSymbol measurementContainer if (isValid) { var typeRef = TypeReference.Create(valueType); - logger?.Debug($"Found valid instrument type: Func -> IEnumerable -> Measurement -> {typeRef}"); return (true, true, true, true, typeRef); } @@ -356,7 +319,6 @@ funcArg is INamedTypeSymbol funcMeasurementType if (isValid) { var typeRef = TypeReference.Create(funcMeasurementType.TypeArguments[0]); - logger?.Debug($"Found valid instrument type: Func -> Measurement -> {typeRef}"); return (true, false, true, true, typeRef); } @@ -368,7 +330,6 @@ funcArg is INamedTypeSymbol funcMeasurementType if (isValidDirect) { var directRef = TypeReference.Create(funcArg); - logger?.Debug($"Found valid instrument type: Func -> {directRef}"); return (true, false, false, true, directRef); } diff --git a/src/src/SourceGenerator/Helpers/LogMethodModelBuilder.cs b/src/src/SourceGenerator/Helpers/LogMethodModelBuilder.cs index cbe43bf4..a4668bcd 100644 --- a/src/src/SourceGenerator/Helpers/LogMethodModelBuilder.cs +++ b/src/src/SourceGenerator/Helpers/LogMethodModelBuilder.cs @@ -20,7 +20,6 @@ public static ImmutableArray BuildLogMethods( LoggerAttributeData loggerTarget, Compilation compilation, INamedTypeSymbol interfaceSymbol, - ISourceGenLogger? logger, int interfaceGenerationMode, CancellationToken token ) @@ -30,11 +29,8 @@ CancellationToken token List methodTargets = []; foreach (var method in PipelineHelpers.GetAllInterfaceMethods(interfaceSymbol, token)) { - if (Utilities.ContainsAttribute(method, TemplateLibrary.Shared.ExcludeAttribute, token)) - { - logger?.Debug($"Skipping {interfaceSymbol.Name}.{method.Name}, explicitly excluded."); + if (Utilities.ContainsAttribute(method, TypeLibrary.TelemetryShared.ExcludeAttribute, token)) continue; - } // For multi-target interfaces (generationType != GenerationType.Logging means interface has multiple targets): // - Include method ONLY if it has an explicit Logging attribute @@ -45,12 +41,7 @@ CancellationToken token var hasLoggingAttribute = SharedHelpers.GetLogAttribute(method, token) != null; if (!hasLoggingAttribute) - { - logger?.Debug( - $"Skipping {interfaceSymbol.Name}.{method.Name} from logging - no explicit Logging attribute on multi-target interface." - ); continue; - } } if (method.Arity > 0) @@ -58,19 +49,11 @@ CancellationToken token continue; } - logger?.Debug($"Found method {interfaceSymbol.Name}.{method.Name}."); - // Validate return type - don't skip; let through with UnknownReturnType flag so the emitter can report the diagnostic var invalidReturnType = TelemetryRules.IsInvalidLogReturnType(method, token); var isScoped = TypeLibrary.System.IDisposable.Equals(method.ReturnType); - var methodParameters = GetLogMethodParameters( - method, - compilation, - logger, - token, - out var hasParameterError - ); + var methodParameters = GetLogMethodParameters(method, compilation, token, out var hasParameterError); if (hasParameterError) { // LogProperties + ExpandEnumerable conflict: add invalid stub so emitter can report TSG2006 @@ -119,12 +102,6 @@ out var hasParameterError generationType, GenerationType.Logging ); - if (targetGenerationState.RaiseMissingInterfaceSource) - { - logger?.Debug( - $"Identified {interfaceSymbol.Name}.{method.Name} as problematic as the interface is missing source attribute(s) for the method's target(s)." - ); - } // Resolve per-method generation mode. // Priority: method GenerationMode > interface/assembly GenerationMode > Auto (per-method param analysis). @@ -430,7 +407,6 @@ ImmutableArray methodParameters static ImmutableArray GetLogMethodParameters( IMethodSymbol method, Compilation compilation, - ISourceGenLogger? logger, CancellationToken token, out bool hasError ) @@ -453,7 +429,6 @@ out bool hasError || parameterType.Identity.Equals(TypeLibrary.System.TagList) ) { - logger?.Debug($"Skipping parameter '{parameter.Name}' of type '{parameterType}' from logging."); continue; } @@ -484,9 +459,6 @@ out bool hasError ) ) { - logger?.Debug( - $"Skipping property {propertyName} on {parameter.Name} as it is marked with {TypeLibrary.Logging.MicrosoftExtensions.LogPropertyIgnoreAttribute}." - ); continue; } @@ -524,8 +496,6 @@ out bool hasError isFirstException = false; } - logger?.Debug($"Found {parameters.Count} parameter(s) for {method.Name}."); - return [.. parameters]; } } diff --git a/src/src/SourceGenerator/Helpers/PipelineHelpers.Activities.cs b/src/src/SourceGenerator/Helpers/PipelineHelpers.Activities.cs index c7c878cd..d94ad0f4 100644 --- a/src/src/SourceGenerator/Helpers/PipelineHelpers.Activities.cs +++ b/src/src/SourceGenerator/Helpers/PipelineHelpers.Activities.cs @@ -11,33 +11,25 @@ partial class PipelineHelpers public static GeneratorResult BuildActivityTransform( GeneratorAttributeSyntaxContext context, - ISourceGenLogger? logger, CancellationToken token - ) => - BuildActivityTarget(context.TargetSymbol as INamedTypeSymbol, context.SemanticModel.Compilation, logger, token); + ) => BuildActivityTarget(context.TargetSymbol as INamedTypeSymbol, context.SemanticModel.Compilation, token); public static GeneratorResult BuildActivityTarget( INamedTypeSymbol? interfaceSymbol, Compilation compilation, - ISourceGenLogger? logger, CancellationToken token ) { token.ThrowIfCancellationRequested(); if (interfaceSymbol is null) - { - logger?.Fatal($"Could not find the interface symbol for an Activity target."); return GeneratorResult.Empty; - } if (interfaceSymbol.Arity > 0) { - logger?.Diagnostic($"Cannot generate a Activity target for a generic interface '{interfaceSymbol.Name}'."); - return GeneratorResult.Create( DiagnosticInfo.Create( - TelemetryRules.ToDescriptor(DiagnosticLibrary.General.GenericInterfacesNotSupported), + DiagnosticLibrary.General.GenericInterfacesNotSupported.Descriptor, interfaceSymbol ) ); @@ -45,12 +37,7 @@ CancellationToken token var activitySourceData = SharedHelpers.GetActivitySourceAttribute(interfaceSymbol, token); if (activitySourceData is not { } activitySourceAttribute) - { - logger?.Fatal( - $"Could not find {TemplateLibrary.Activities.ActivitySourceAttribute} when one was expected '{interfaceSymbol.Name}'." - ); return GeneratorResult.Empty; - } var telemetryGeneration = SharedHelpers.GetTelemetryGenerationAttribute(interfaceSymbol, compilation, token); var className = telemetryGeneration.ClassName ?? GenerateClassName(interfaceSymbol.Name); @@ -79,12 +66,8 @@ CancellationToken token interfaceSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(token) is not InterfaceDeclarationSyntax interfaceDeclaration ) - { - logger?.Fatal($"Could not locate the declaring syntax for '{interfaceSymbol.Name}'."); return GeneratorResult.Empty; - } - var fullNamespace = Utilities.GetFullNamespace(interfaceDeclaration, true); var generationType = SharedHelpers.GetGenerationTypes(interfaceSymbol, token); var activityMethods = BuildActivityMethods( generationType, @@ -92,7 +75,6 @@ is not InterfaceDeclarationSyntax interfaceDeclaration activitySourceGenerationAttribute, telemetryGeneration, interfaceSymbol, - logger, token ); @@ -101,10 +83,7 @@ is not InterfaceDeclarationSyntax interfaceDeclaration TelemetryGeneration: telemetryGeneration, GenerationType: generationType, ClassNameToGenerate: className, - ClassNamespace: Utilities.GetNamespace(interfaceDeclaration), ParentClasses: Utilities.GetParentClasses(interfaceDeclaration), - FullNamespace: fullNamespace, - FullyQualifiedName: fullNamespace + className, InterfaceType: TypeReference.Create(interfaceSymbol), ActivitySourceGenerationAttribute: activitySourceGenerationAttribute, ActivitySourceName: activitySourceName, @@ -121,7 +100,6 @@ static ImmutableArray BuildActivityMethods( ActivitySourceGenerationAttributeData? activitySourceGenerationAttribute, TelemetryGenerationAttributeData telemetryGeneration, INamedTypeSymbol interfaceSymbol, - ISourceGenLogger? logger, CancellationToken token ) { @@ -165,15 +143,11 @@ CancellationToken token continue; } - if (Utilities.ContainsAttribute(method, TemplateLibrary.Shared.ExcludeAttribute, token)) - { - logger?.Debug($"Skipping {interfaceSymbol.Name}.{method.Name}, explicitly excluded."); + if (TypeHelpers.HasAttribute(method, TypeLibrary.TelemetryShared.ExcludeAttribute)) continue; - } var (methodType, isInferred) = GetMethodType( method, - logger, token, out var activityAttribute, out var eventAttribute @@ -183,15 +157,12 @@ out var eventAttribute if (string.IsNullOrWhiteSpace(activityOrEventName)) activityOrEventName = method.Name; - logger?.Debug($"Found {methodType} method {interfaceSymbol.Name}.{method.Name}."); - var parameters = GetActivityParameters( method, prefix, defaultToTags, lowercaseBaggageAndTagKeys, namingConvention, - logger, token ); var baggageParameters = parameters @@ -206,12 +177,6 @@ out var eventAttribute generationType, GenerationType.Activities ); - if (targetGenerationState.RaiseMissingInterfaceSource) - { - logger?.Debug( - $"Identified {interfaceSymbol.Name}.{method.Name} as problematic as the interface is missing source attribute(s) for the method's target(s)." - ); - } methodTargets.Add( new( @@ -241,7 +206,6 @@ static ImmutableArray GetActivityParameters( bool defaultToTags, bool lowercaseBaggageAndTagKeys, int namingConvention, - ISourceGenLogger? logger, CancellationToken token ) { @@ -251,19 +215,11 @@ CancellationToken token token.ThrowIfCancellationRequested(); var parameterType = TypeReference.Create(parameter.Type); - var (destination, attribute) = GetParameterDestination( - parameter, - parameterType, - defaultToTags, - logger, - token - ); + var (destination, attribute) = GetParameterDestination(parameter, parameterType, defaultToTags, token); TagOrBaggageAttributeRecord? tagOrBaggageAttribute = null; if (attribute != null) - { tagOrBaggageAttribute = SharedHelpers.GetTagOrBaggageAttribute(attribute, token); - } // Check for ExcludeTargetsAttribute var excludeTargets = SharedHelpers.GetExcludeTargetsAttribute(parameter, token); @@ -294,7 +250,6 @@ CancellationToken token static (ActivityMethodType, bool) GetMethodType( IMethodSymbol method, - ISourceGenLogger? logger, CancellationToken token, out ActivityAttributeData? activityAttribute, out EventAttributeData? eventAttribute @@ -306,56 +261,33 @@ out EventAttributeData? eventAttribute activityAttribute = SharedHelpers.GetActivityGenAttribute(method, token); if (activityAttribute != null) - { - logger?.Debug($"Found explicit activity: {method.Name}."); return (ActivityMethodType.Activity, false); - } eventAttribute = SharedHelpers.GetActivityEventAttribute(method, token); if (eventAttribute != null) - { - logger?.Debug($"Found explicit event: {method.Name}."); return (ActivityMethodType.Event, false); - } - if (Utilities.ContainsAttribute(method, TemplateLibrary.Activities.ContextAttribute, token)) - { - logger?.Debug($"Found explicit context: {method.Name}."); + if (Utilities.ContainsAttribute(method, TypeLibrary.Activities.ContextAttribute, token)) return (ActivityMethodType.Context, false); - } var returnType = method.ReturnType; if (TypeLibrary.Activities.SystemDiagnostics.Activity.Equals(returnType)) - { - logger?.Debug($"Inferring activity due to return type ({returnType.ToDisplayString()}): {method.Name}."); return (ActivityMethodType.Activity, true); - } if (method.Name.EndsWith("Event", StringComparison.Ordinal)) - { - logger?.Debug($"Inferring event as the method name ends in 'Event': {method.Name}."); return (ActivityMethodType.Event, true); - } else { if ( method.Parameters.Length > 0 && TypeLibrary.Activities.SystemDiagnostics.Activity.Equals(method.Parameters[0].Type) ) - { - logger?.Debug($"Inferring event as the method's first parameter is an Activity: {method.Name}."); - return (ActivityMethodType.Event, true); - } } if (method.Name.EndsWith("Context", StringComparison.Ordinal)) - { - logger?.Debug($"Inferring context as the method name ends in 'Context': {method.Name}."); return (ActivityMethodType.Context, true); - } - logger?.Debug($"Defaulting to activity: {method.Name}."); return (ActivityMethodType.Activity, true); } @@ -363,44 +295,36 @@ out EventAttributeData? eventAttribute IParameterSymbol parameter, TypeReference parameterType, bool defaultToTags, - ISourceGenLogger? logger, CancellationToken token ) { var destination = defaultToTags ? ActivityParameterDestination.Tag : ActivityParameterDestination.Baggage; - if (Utilities.TryContainsAttribute(parameter, TemplateLibrary.Shared.TagAttribute, token, out var attribute)) - { - logger?.Debug($"Found explicit tag: {parameter.Name}."); - return (ActivityParameterDestination.Tag, attribute); - } - if ( - Utilities.TryContainsAttribute(parameter, TemplateLibrary.Activities.BaggageAttribute, token, out attribute) + Utilities.TryContainsAttribute( + parameter, + TypeLibrary.TelemetryShared.TagAttribute, + token, + out var attribute + ) ) - { - logger?.Debug($"Found explicit baggage: {parameter.Name}."); + return (ActivityParameterDestination.Tag, attribute); + + if (Utilities.TryContainsAttribute(parameter, TypeLibrary.Activities.BaggageAttribute, token, out attribute)) return (ActivityParameterDestination.Baggage, attribute); - } - if (Utilities.ContainsAttribute(parameter, TemplateLibrary.Activities.EscapeAttribute, token)) - { - logger?.Debug($"Found escape parameter: {parameter.Name}."); + if (Utilities.ContainsAttribute(parameter, TypeLibrary.Activities.EscapeAttribute, token)) return (ActivityParameterDestination.Escape, null); - } - if (Utilities.ContainsAttribute(parameter, TemplateLibrary.Activities.StatusDescriptionAttribute, token)) - { - logger?.Debug($"Found status description parameter: {parameter.Name}."); + if (Utilities.ContainsAttribute(parameter, TypeLibrary.Activities.StatusDescriptionAttribute, token)) return (ActivityParameterDestination.StatusDescription, null); - } if (parameterType.Identity.Equals(TypeLibrary.Activities.SystemDiagnostics.Activity)) return (ActivityParameterDestination.Activity, null); if ( parameterType.Identity.Equals(TypeLibrary.Activities.SystemDiagnostics.ActivityTagsCollection) - || TypeLibrary.Activities.SystemDiagnostics.ActivityTagIEnumerable.Equals(parameterType) + || TypeLibrary.Activities.SystemDiagnostics.ActivityTagIEnumerable.Similar(parameterType) || parameterType.Identity.Equals(TypeLibrary.System.TagList) ) return (ActivityParameterDestination.TagsEnumerable, null); @@ -433,7 +357,6 @@ CancellationToken token return (ActivityParameterDestination.Timestamp, null); // Infer tag/baggage based on the interface default. - logger?.Debug($"Inferring {(defaultToTags ? "tag" : "baggage")}: {parameter.Name}."); return (destination, null); } diff --git a/src/src/SourceGenerator/Helpers/PipelineHelpers.Logger.cs b/src/src/SourceGenerator/Helpers/PipelineHelpers.Logger.cs index 9088dd40..528dd960 100644 --- a/src/src/SourceGenerator/Helpers/PipelineHelpers.Logger.cs +++ b/src/src/SourceGenerator/Helpers/PipelineHelpers.Logger.cs @@ -10,43 +10,31 @@ partial class PipelineHelpers public static GeneratorResult BuildLoggerTransform( GeneratorAttributeSyntaxContext context, - ISourceGenLogger? logger, CancellationToken token - ) => BuildLoggerTarget(context.TargetSymbol as INamedTypeSymbol, context.SemanticModel.Compilation, logger, token); + ) => BuildLoggerTarget(context.TargetSymbol as INamedTypeSymbol, context.SemanticModel.Compilation, token); public static GeneratorResult BuildLoggerTarget( INamedTypeSymbol? interfaceSymbol, Compilation compilation, - ISourceGenLogger? logger, CancellationToken token ) { token.ThrowIfCancellationRequested(); if (interfaceSymbol is null) - { - logger?.Fatal($"Could not find the interface symbol for a Logger target."); return GeneratorResult.Empty; - } var iLoggerTypeSymbol = compilation.GetTypeByMetadataName( TypeLibrary.Logging.MicrosoftExtensions.ILogger.MetadataFullName ); if (iLoggerTypeSymbol is null) - { - logger?.Diagnostic( - $"Requested a Logger target to be generated, but could not find the ILogger symbol referenced '{interfaceSymbol.Name}'." - ); return GeneratorResult.Empty; - } if (interfaceSymbol.Arity > 0) { - logger?.Diagnostic($"Cannot generate a Logger target for a generic interface '{interfaceSymbol.Name}'."); - return GeneratorResult.Create( DiagnosticInfo.Create( - TelemetryRules.ToDescriptor(DiagnosticLibrary.General.GenericInterfacesNotSupported), + DiagnosticLibrary.General.GenericInterfacesNotSupported.Descriptor, interfaceSymbol ) ); @@ -54,12 +42,7 @@ CancellationToken token var loggerData = SharedHelpers.GetLoggerAttribute(interfaceSymbol, token); if (loggerData is not { } loggerAttribute) - { - logger?.Fatal( - $"Could not find {TemplateLibrary.Logging.LoggerAttribute} when one was expected '{interfaceSymbol.Name}'." - ); return GeneratorResult.Empty; - } var telemetryGeneration = SharedHelpers.GetTelemetryGenerationAttribute(interfaceSymbol, compilation, token); var className = telemetryGeneration.ClassName ?? GenerateClassName(interfaceSymbol.Name); @@ -79,11 +62,9 @@ CancellationToken token is not InterfaceDeclarationSyntax interfaceDeclaration ) { - logger?.Fatal($"Could not locate the declaring syntax for '{interfaceSymbol.Name}'."); return GeneratorResult.Empty; } - var fullNamespace = Utilities.GetFullNamespace(interfaceDeclaration, true); var logMethods = LogMethodModelBuilder.BuildLogMethods( generationType, className, @@ -92,7 +73,6 @@ is not InterfaceDeclarationSyntax interfaceDeclaration loggerAttribute, compilation, interfaceSymbol, - logger, interfaceGenerationMode: interfaceGenerationMode, token ); @@ -102,10 +82,7 @@ is not InterfaceDeclarationSyntax interfaceDeclaration TelemetryGeneration: telemetryGeneration, GenerationType: generationType, ClassNameToGenerate: className, - ClassNamespace: Utilities.GetNamespace(interfaceDeclaration), ParentClasses: Utilities.GetParentClasses(interfaceDeclaration), - FullNamespace: fullNamespace, - FullyQualifiedName: fullNamespace + className, InterfaceType: TypeReference.Create(interfaceSymbol), LoggerAttribute: loggerAttribute, DefaultLevel: defaultLogLevel, diff --git a/src/src/SourceGenerator/Helpers/PipelineHelpers.Metrics.cs b/src/src/SourceGenerator/Helpers/PipelineHelpers.Metrics.cs index c3cde75a..2defba9c 100644 --- a/src/src/SourceGenerator/Helpers/PipelineHelpers.Metrics.cs +++ b/src/src/SourceGenerator/Helpers/PipelineHelpers.Metrics.cs @@ -10,32 +10,25 @@ partial class PipelineHelpers public static GeneratorResult BuildMeterTransform( GeneratorAttributeSyntaxContext context, - ISourceGenLogger? logger, CancellationToken token - ) => BuildMeterTarget(context.TargetSymbol as INamedTypeSymbol, context.SemanticModel.Compilation, logger, token); + ) => BuildMeterTarget(context.TargetSymbol as INamedTypeSymbol, context.SemanticModel.Compilation, token); public static GeneratorResult BuildMeterTarget( INamedTypeSymbol? interfaceSymbol, Compilation compilation, - ISourceGenLogger? logger, CancellationToken token ) { token.ThrowIfCancellationRequested(); if (interfaceSymbol is null) - { - logger?.Fatal($"Could not find the interface symbol for a Meter target."); return GeneratorResult.Empty; - } if (interfaceSymbol.Arity > 0) { - logger?.Diagnostic($"Cannot generate a Meter target for a generic interface '{interfaceSymbol.Name}'."); - return GeneratorResult.Create( DiagnosticInfo.Create( - TelemetryRules.ToDescriptor(DiagnosticLibrary.General.GenericInterfacesNotSupported), + DiagnosticLibrary.General.GenericInterfacesNotSupported.Descriptor, interfaceSymbol ) ); @@ -43,12 +36,7 @@ CancellationToken token var meterData = SharedHelpers.GetMeterAttribute(interfaceSymbol, token); if (meterData is not { } meterAttribute) - { - logger?.Fatal( - $"Could not find {TemplateLibrary.Metrics.MeterAttribute} when one was expected '{interfaceSymbol.Name}'." - ); return GeneratorResult.Empty; - } var telemetryGeneration = SharedHelpers.GetTelemetryGenerationAttribute(interfaceSymbol, compilation, token); var className = telemetryGeneration.ClassName ?? GenerateClassName(interfaceSymbol.Name); @@ -59,12 +47,9 @@ CancellationToken token is not InterfaceDeclarationSyntax interfaceDeclaration ) { - logger?.Fatal($"Could not locate the declaring syntax for '{interfaceSymbol.Name}'."); return GeneratorResult.Empty; } - var fullNamespace = Utilities.GetFullNamespace(interfaceDeclaration, true); - var meterName = meterAttribute.Name; if (string.IsNullOrWhiteSpace(meterName)) { @@ -99,7 +84,6 @@ is not InterfaceDeclarationSyntax interfaceDeclaration telemetryGeneration, meterName!, interfaceSymbol, - logger, token ); @@ -108,10 +92,7 @@ is not InterfaceDeclarationSyntax interfaceDeclaration TelemetryGeneration: telemetryGeneration, GenerationType: generationType, ClassNameToGenerate: className, - ClassNamespace: Utilities.GetNamespace(interfaceDeclaration), ParentClasses: Utilities.GetParentClasses(interfaceDeclaration), - FullNamespace: fullNamespace, - FullyQualifiedName: fullNamespace + className, InterfaceType: TypeReference.Create(interfaceSymbol), MeterName: meterName, MeterGeneration: meterGenerationAttribute, diff --git a/src/src/SourceGenerator/Helpers/PropertyLibrary.cs b/src/src/SourceGenerator/Helpers/PropertyLibrary.cs index 78cceea9..1a7c08f6 100644 --- a/src/src/SourceGenerator/Helpers/PropertyLibrary.cs +++ b/src/src/SourceGenerator/Helpers/PropertyLibrary.cs @@ -36,49 +36,13 @@ static class PropertyLibrary RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace ); - // Template header: used only when loading the shipped marker-attribute templates. - const string GeneratedCodeConstant = - "[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Purview.Telemetry.SourceGenerator\", \"{0}\")]"; - - public static readonly Lazy TemplateGeneratedCode = new(() => - string.Format(global::System.Globalization.CultureInfo.InvariantCulture, GeneratedCodeConstant, Version.Value) - ); - - public static class BuiltInTypes - { - public const string StringKeyword = "string"; - public const string ObjectKeyword = "object"; - public const string BoolKeyword = "bool"; - public const string ByteKeyword = "byte"; - public const string ShortKeyword = "short"; - public const string IntKeyword = "int"; - public const string LongKeyword = "long"; - public const string SByteKeyword = "sbyte"; - public const string UShortKeyword = "ushort"; - public const string UIntKeyword = "uint"; - public const string ULongKeyword = "ulong"; - public const string FloatKeyword = "float"; - public const string DoubleKeyword = "double"; - public const string DecimalKeyword = "decimal"; - public const string CharKeyword = "char"; - } - public static class System { - public const string VoidKeyword = "void"; public const string NullKeyword = "null"; - public const string DefaultKeyword = "default"; } // TEMPORARY attribute strings still referenced by emitters; removed during the // structured-emission pass. - public const string AggressiveInlining = - "[global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]"; - public const string ExcludeFromCodeCoverageConstant = - "[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute]"; - public const string EditorBrowsableConstant = - "[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]"; - public static class Activities { public const bool UseRecordExceptionRulesDefault = true; @@ -209,26 +173,15 @@ public static class Metrics public static string UpDownCounterMetadataName => TelemetryAttributeNames.Metrics.UpDownCounter.MetadataFullName; - public static readonly string[] ValidMeasurementKeywordTypes = + public static readonly TypeMapping[] ValidMeasurementKeywordTypes = [ - BuiltInTypes.ByteKeyword, - BuiltInTypes.ShortKeyword, - BuiltInTypes.IntKeyword, - BuiltInTypes.LongKeyword, - BuiltInTypes.DoubleKeyword, - BuiltInTypes.FloatKeyword, - BuiltInTypes.DecimalKeyword, - ]; - - public static readonly SpecialType[] ValidMeasurementSpecialTypes = - [ - SpecialType.System_Byte, - SpecialType.System_Int16, - SpecialType.System_Int32, - SpecialType.System_Int64, - SpecialType.System_Double, - SpecialType.System_Single, - SpecialType.System_Decimal, + KnownLangTypes.Get(SpecialType.System_Byte), + KnownLangTypes.Get(SpecialType.System_Int16), + KnownLangTypes.Get(SpecialType.System_Int32), + KnownLangTypes.Get(SpecialType.System_Int64), + KnownLangTypes.Get(SpecialType.System_Double), + KnownLangTypes.Get(SpecialType.System_Single), + KnownLangTypes.Get(SpecialType.System_Decimal), ]; } @@ -239,13 +192,4 @@ public static class DependencyInjection "Singleton" ); } - - /// - /// Naming convention strategies for generated telemetry names. - /// - public enum NamingConvention - { - Legacy = 0, - OpenTelemetry = 1, - } } diff --git a/src/src/SourceGenerator/Helpers/SharedHelpers.Activities.cs b/src/src/SourceGenerator/Helpers/SharedHelpers.Activities.cs index d9dacbb0..41c3f1a5 100644 --- a/src/src/SourceGenerator/Helpers/SharedHelpers.Activities.cs +++ b/src/src/SourceGenerator/Helpers/SharedHelpers.Activities.cs @@ -15,7 +15,7 @@ CancellationToken token if ( !Utilities.TryContainsAttribute( symbol, - TemplateLibrary.Activities.ActivitySourceAttribute, + TypeLibrary.Activities.ActivitySourceAttribute, token, out var attributeData ) @@ -42,7 +42,7 @@ CancellationToken token if ( !Utilities.TryContainsAttribute( symbol, - TemplateLibrary.Activities.ActivitySourceGenerationAttribute, + TypeLibrary.Activities.ActivitySourceGenerationAttribute, token, out var attributeData ) @@ -67,7 +67,7 @@ out var attributeData if ( !Utilities.TryContainsAttribute( symbol, - TemplateLibrary.Activities.ActivityAttribute, + TypeLibrary.Activities.ActivityAttribute, token, out var attributeData ) @@ -83,12 +83,7 @@ out var attributeData public static EventAttributeData? GetActivityEventAttribute(ISymbol symbol, CancellationToken token) { if ( - !Utilities.TryContainsAttribute( - symbol, - TemplateLibrary.Activities.EventAttribute, - token, - out var attributeData - ) + !Utilities.TryContainsAttribute(symbol, TypeLibrary.Activities.EventAttribute, token, out var attributeData) ) { return null; @@ -106,8 +101,8 @@ out var attributeData public static bool IsActivityMethod(IMethodSymbol method, CancellationToken token) { - return Utilities.ContainsAttribute(method, TemplateLibrary.Activities.ActivityAttribute, token) - || Utilities.ContainsAttribute(method, TemplateLibrary.Activities.EventAttribute, token) - || Utilities.ContainsAttribute(method, TemplateLibrary.Activities.ContextAttribute, token); + return Utilities.ContainsAttribute(method, TypeLibrary.Activities.ActivityAttribute, token) + || Utilities.ContainsAttribute(method, TypeLibrary.Activities.EventAttribute, token) + || Utilities.ContainsAttribute(method, TypeLibrary.Activities.ContextAttribute, token); } } diff --git a/src/src/SourceGenerator/Helpers/SharedHelpers.Logging.cs b/src/src/SourceGenerator/Helpers/SharedHelpers.Logging.cs index 33b60ee7..c59d4a03 100644 --- a/src/src/SourceGenerator/Helpers/SharedHelpers.Logging.cs +++ b/src/src/SourceGenerator/Helpers/SharedHelpers.Logging.cs @@ -1,6 +1,5 @@ using Microsoft.CodeAnalysis; using Purview.Telemetry.SourceGenerator.Records; -using Purview.Telemetry.SourceGenerator.Templates; namespace Purview.Telemetry.SourceGenerator.Helpers; @@ -11,17 +10,17 @@ partial class SharedHelpers if ( !Utilities.TryContainsAttribute( symbol, - [TemplateLibrary.Logging.LogAttribute, .. TemplateLibrary.Logging.SpecificLogAttributes], + TypeLibrary.Logging.LogAttributeTargets, token, - out var attributeData, - out var matchingTemplate + out var matchingType, + out var attributeData ) ) { return null; } - if (matchingTemplate == TemplateLibrary.Logging.LogAttribute) + if (matchingType == TypeLibrary.Logging.LogAttribute) { var data = LogAttributeData.FromAttributeData(attributeData!); return data.Exists @@ -33,101 +32,48 @@ out var matchingTemplate : null; } - // A specific level attribute (Trace/Debug/Info/Warning/Error/Critical) forces its level. - (var messageTemplate, var eventId, var name, var generationMode) = GetSpecificLogData( - matchingTemplate!, - attributeData! - ); - - return new( - exists: true, - Level: TemplateLibrary.Logging.SpecificLogAttributesToLevel[matchingTemplate!], - MessageTemplate: messageTemplate, - EventId: eventId, - Name: name, - GenerationMode: generationMode - ); + return GetSpecificLogData(matchingType, attributeData!); } - static (string? MessageTemplate, int EventId, string? Name, int GenerationMode) GetSpecificLogData( - TemplateInfo template, - AttributeData attributeData - ) + static LogAttributeData GetSpecificLogData(TypeIdentity template, AttributeData attributeData) { - if (template == TemplateLibrary.Logging.TraceAttribute) + if (template == TypeLibrary.Logging.TraceAttribute) { var data = TraceAttributeData.FromAttributeData(attributeData); - return ( - NullIfWhitespace(data.MessageTemplate), - data.EventId, - NullIfWhitespace(data.Name), - data.GenerationMode - ); + return data.ToLogAttribute(); } - if (template == TemplateLibrary.Logging.DebugAttribute) + if (template == TypeLibrary.Logging.DebugAttribute) { var data = DebugAttributeData.FromAttributeData(attributeData); - return ( - NullIfWhitespace(data.MessageTemplate), - data.EventId, - NullIfWhitespace(data.Name), - data.GenerationMode - ); + return data.ToLogAttribute(); } - if (template == TemplateLibrary.Logging.InfoAttribute) + if (template == TypeLibrary.Logging.InfoAttribute) { var data = InfoAttributeData.FromAttributeData(attributeData); - return ( - NullIfWhitespace(data.MessageTemplate), - data.EventId, - NullIfWhitespace(data.Name), - data.GenerationMode - ); + return data.ToLogAttribute(); } - if (template == TemplateLibrary.Logging.WarningAttribute) + if (template == TypeLibrary.Logging.WarningAttribute) { var data = WarningAttributeData.FromAttributeData(attributeData); - return ( - NullIfWhitespace(data.MessageTemplate), - data.EventId, - NullIfWhitespace(data.Name), - data.GenerationMode - ); + return data.ToLogAttribute(); } - if (template == TemplateLibrary.Logging.ErrorAttribute) + if (template == TypeLibrary.Logging.ErrorAttribute) { var data = ErrorAttributeData.FromAttributeData(attributeData); - return ( - NullIfWhitespace(data.MessageTemplate), - data.EventId, - NullIfWhitespace(data.Name), - data.GenerationMode - ); + return data.ToLogAttribute(); } var criticalData = CriticalAttributeData.FromAttributeData(attributeData); - return ( - NullIfWhitespace(criticalData.MessageTemplate), - criticalData.EventId, - NullIfWhitespace(criticalData.Name), - criticalData.GenerationMode - ); + return criticalData.ToLogAttribute(); } public static LoggerAttributeData? GetLoggerAttribute(ISymbol symbol, CancellationToken token) { - if ( - !Utilities.TryContainsAttribute( - symbol, - TemplateLibrary.Logging.LoggerAttribute, - token, - out var attributeData - ) - ) + if (!Utilities.TryContainsAttribute(symbol, TypeLibrary.Logging.LoggerAttribute, token, out var attributeData)) { return null; } @@ -141,7 +87,7 @@ out var attributeData if ( !Utilities.TryContainsAttribute( symbol, - TemplateLibrary.Logging.LoggerGenerationAttribute, + TypeLibrary.Logging.LoggerGenerationAttribute, token, out var attributeData ) @@ -177,7 +123,7 @@ out var attributeData if ( !Utilities.TryContainsAttribute( symbol, - TemplateLibrary.Logging.ExpandEnumerableAttribute, + TypeLibrary.Logging.ExpandEnumerableAttribute, token, out var attributeData ) @@ -195,13 +141,6 @@ out var attributeData CancellationToken token ) => GetLoggerGenerationAttribute(compilation.Assembly, token); - public static bool IsLogMethod(IMethodSymbol method, CancellationToken token) => - Utilities.ContainsAttribute( - method, - [TemplateLibrary.Logging.LogAttribute, .. TemplateLibrary.Logging.SpecificLogAttributes], - token - ); - /// /// Returns a non-randomized hash code for the given string. /// diff --git a/src/src/SourceGenerator/Helpers/SharedHelpers.Metrics.cs b/src/src/SourceGenerator/Helpers/SharedHelpers.Metrics.cs index 21f83cfa..3338f0b5 100644 --- a/src/src/SourceGenerator/Helpers/SharedHelpers.Metrics.cs +++ b/src/src/SourceGenerator/Helpers/SharedHelpers.Metrics.cs @@ -12,14 +12,7 @@ CancellationToken token public static MeterAttributeData? GetMeterAttribute(ISymbol symbol, CancellationToken token) { - if ( - !Utilities.TryContainsAttribute( - symbol, - TemplateLibrary.Metrics.MeterAttribute, - token, - out var attributeData - ) - ) + if (!Utilities.TryContainsAttribute(symbol, TypeLibrary.Metrics.MeterAttribute, token, out var attributeData)) { return null; } @@ -39,7 +32,7 @@ out var attributeData if ( !Utilities.TryContainsAttribute( symbol, - TemplateLibrary.Metrics.MeterGenerationAttribute, + TypeLibrary.Metrics.MeterGenerationAttribute, token, out var attributeData ) @@ -59,63 +52,27 @@ out var attributeData : null; } - public static InstrumentAttributeRecord? GetInstrumentAttribute( - ISymbol symbol, - ISourceGenLogger? logger, - CancellationToken token - ) + public static InstrumentAttributeRecord? GetInstrumentAttribute(ISymbol symbol, CancellationToken token) { - AttributeData? attributeData = null; - foreach (var instrumentAttribute in TemplateLibrary.Metrics.ValidInstrumentAttributes) - { - if (Utilities.TryContainsAttribute(symbol, instrumentAttribute, token, out attributeData)) - { - break; - } - } - - if (attributeData?.AttributeClass == null) - return null; - - var attributeType = TypeReference.Create(attributeData.AttributeClass); - - var record = attributeType switch - { - _ when TemplateLibrary.Metrics.CounterAttribute == attributeType => ToRecord( - CounterAttributeData.FromAttributeData(attributeData), - InstrumentTypes.Counter - ), - _ when TemplateLibrary.Metrics.AutoCounterAttribute == attributeType => ToRecord( - AutoCounterAttributeData.FromAttributeData(attributeData), - InstrumentTypes.Counter - ), - _ when TemplateLibrary.Metrics.UpDownCounterAttribute == attributeType => ToRecord( - UpDownCounterAttributeData.FromAttributeData(attributeData), - InstrumentTypes.UpDownCounter - ), - _ when TemplateLibrary.Metrics.HistogramAttribute == attributeType => ToRecord( - HistogramAttributeData.FromAttributeData(attributeData), - InstrumentTypes.Histogram - ), - _ when TemplateLibrary.Metrics.ObservableCounterAttribute == attributeType => ToRecord( - ObservableCounterAttributeData.FromAttributeData(attributeData), - InstrumentTypes.ObservableCounter - ), - _ when TemplateLibrary.Metrics.ObservableUpDownCounterAttribute == attributeType => ToRecord( - ObservableUpDownCounterAttributeData.FromAttributeData(attributeData), - InstrumentTypes.ObservableUpDownCounter - ), - _ when TemplateLibrary.Metrics.ObservableGaugeAttribute == attributeType => ToRecord( - ObservableGaugeAttributeData.FromAttributeData(attributeData), - InstrumentTypes.ObservableGauge - ), - _ => null, - }; - - if (record is null) - logger?.Fatal($"Unknown instrument type {attributeType}."); - - return record; + token.ThrowIfCancellationRequested(); + + if (CounterAttributeData.TryFromAttributeData(symbol, out var counterAttribute)) + return ToRecord(counterAttribute, InstrumentTypes.Counter); + if (AutoCounterAttributeData.TryFromAttributeData(symbol, out var autoCounterAttribute)) + return ToRecord(autoCounterAttribute, InstrumentTypes.Counter); + if (UpDownCounterAttributeData.TryFromAttributeData(symbol, out var upDownCounterAttribute)) + return ToRecord(upDownCounterAttribute, InstrumentTypes.UpDownCounter); + if (HistogramAttributeData.TryFromAttributeData(symbol, out var histogramAttribute)) + return ToRecord(histogramAttribute, InstrumentTypes.Histogram); + if (ObservableCounterAttributeData.TryFromAttributeData(symbol, out var observableCounterAttribute)) + return ToRecord(observableCounterAttribute, InstrumentTypes.ObservableCounter); + if (ObservableUpDownCounterAttributeData.TryFromAttributeData(symbol, out var observableUpDownCounterAttribute)) + return ToRecord(observableUpDownCounterAttribute, InstrumentTypes.ObservableUpDownCounter); + if (ObservableGaugeAttributeData.TryFromAttributeData(symbol, out var observableGaugeAttribute)) + return ToRecord(observableGaugeAttribute, InstrumentTypes.ObservableGauge); + + // No matching instrument attribute found + return null; } static InstrumentAttributeRecord ToRecord( @@ -157,5 +114,5 @@ static InstrumentAttributeRecord ToRecord(ObservableGaugeAttributeData data, Ins ToRecord(data.Name, data.Unit, data.Description, false, data.ThrowOnAlreadyInitialized, type); public static bool IsValidMeasurementValueType(ITypeSymbol type) => - Array.Exists(PropertyLibrary.Metrics.ValidMeasurementSpecialTypes, m => m == type.SpecialType); + PropertyLibrary.Metrics.ValidMeasurementKeywordTypes.Any(m => m.SpecialType == type.SpecialType); } diff --git a/src/src/SourceGenerator/Helpers/SharedHelpers.cs b/src/src/SourceGenerator/Helpers/SharedHelpers.cs index 562734a0..8a36be12 100644 --- a/src/src/SourceGenerator/Helpers/SharedHelpers.cs +++ b/src/src/SourceGenerator/Helpers/SharedHelpers.cs @@ -11,13 +11,13 @@ public static GenerationType GetGenerationTypes(ISymbol symbol, CancellationToke var generationType = GenerationType.None; - if (Utilities.ContainsAttribute(symbol, TemplateLibrary.Activities.ActivitySourceAttribute, token)) + if (Utilities.ContainsAttribute(symbol, TypeLibrary.Activities.ActivitySourceAttribute, token)) generationType |= GenerationType.Activities; - if (Utilities.ContainsAttribute(symbol, TemplateLibrary.Logging.LoggerAttribute, token)) + if (Utilities.ContainsAttribute(symbol, TypeLibrary.Logging.LoggerAttribute, token)) generationType |= GenerationType.Logging; - if (Utilities.ContainsAttribute(symbol, TemplateLibrary.Metrics.MeterAttribute, token)) + if (Utilities.ContainsAttribute(symbol, TypeLibrary.Metrics.MeterAttribute, token)) generationType |= GenerationType.Metrics; return generationType; @@ -114,7 +114,8 @@ CancellationToken token DependencyInjectionClassIsPublic: false, NamingConvention: 1, // Default to OpenTelemetry GenerateTelemetryNamesClass: true, - TelemetryNamesClassName: null + TelemetryNamesClassName: null, + TelemetryNamesNamespace: null ); public static TelemetryGenerationAttributeData GetTelemetryGenerationAttribute( @@ -139,6 +140,7 @@ data with ClassName = NullIfWhitespace(data.ClassName), DependencyInjectionClassName = NullIfWhitespace(data.DependencyInjectionClassName), TelemetryNamesClassName = NullIfWhitespace(data.TelemetryNamesClassName), + TelemetryNamesNamespace = NullIfWhitespace(data.TelemetryNamesNamespace), }; /// @@ -152,7 +154,7 @@ CancellationToken token if ( !Utilities.TryContainsAttribute( parameter, - TemplateLibrary.Shared.ExcludeTargetsAttribute, + TypeLibrary.TelemetryShared.ExcludeTargetsAttribute, token, out var attributeData ) diff --git a/src/src/SourceGenerator/Helpers/TemplateLibrary.cs b/src/src/SourceGenerator/Helpers/TemplateLibrary.cs deleted file mode 100644 index 32cde5d4..00000000 --- a/src/src/SourceGenerator/Helpers/TemplateLibrary.cs +++ /dev/null @@ -1,201 +0,0 @@ -using Purview.Telemetry.SourceGenerator.Templates; - -namespace Purview.Telemetry.SourceGenerator.Helpers; - -/// -/// Registry of the shipped marker-attribute templates injected into consuming compilations. -/// -static class TemplateLibrary -{ - public static class Shared - { - public static readonly TemplateInfo TagAttribute = TemplateInfo.Create(TypeLibrary.Telemetry.TagAttribute); - public static readonly TemplateInfo ExcludeAttribute = TemplateInfo.Create( - TypeLibrary.Telemetry.ExcludeAttribute - ); - public static readonly TemplateInfo TelemetryGenerationAttribute = TemplateInfo.Create( - TypeLibrary.Telemetry.TelemetryGenerationAttribute - ); - public static readonly TemplateInfo TargetsEnum = TemplateInfo.Create(TypeLibrary.Telemetry.TargetsEnum); - public static readonly TemplateInfo NamingConventionEnum = TemplateInfo.Create( - TypeLibrary.Telemetry.NamingConventionEnum - ); - public static readonly TemplateInfo ExcludeTargetsAttribute = TemplateInfo.Create( - TypeLibrary.Telemetry.ExcludeTargetsAttribute - ); - - public static TemplateInfo[] GetTemplates() => - [ - TagAttribute, - ExcludeAttribute, - TelemetryGenerationAttribute, - TargetsEnum, - NamingConventionEnum, - ExcludeTargetsAttribute, - ]; - } - - public static class Activities - { - public static readonly TemplateInfo ActivitySourceGenerationAttribute = TemplateInfo.Create( - TypeLibrary.Activities.ActivitySourceGenerationAttribute - ); - public static readonly TemplateInfo ActivitySourceAttribute = TemplateInfo.Create( - TypeLibrary.Activities.ActivitySourceAttribute - ); - public static readonly TemplateInfo ActivityAttribute = TemplateInfo.Create( - TypeLibrary.Activities.ActivityAttribute - ); - public static readonly TemplateInfo EventAttribute = TemplateInfo.Create(TypeLibrary.Activities.EventAttribute); - public static readonly TemplateInfo ContextAttribute = TemplateInfo.Create( - TypeLibrary.Activities.ContextAttribute - ); - public static readonly TemplateInfo BaggageAttribute = TemplateInfo.Create( - TypeLibrary.Activities.BaggageAttribute - ); - public static readonly TemplateInfo EscapeAttribute = TemplateInfo.Create( - TypeLibrary.Activities.EscapeAttribute - ); - public static readonly TemplateInfo StatusDescriptionAttribute = TemplateInfo.Create( - TypeLibrary.Activities.StatusDescriptionAttribute - ); - - public static TemplateInfo[] GetTemplates() => - [ - ActivitySourceGenerationAttribute, - ActivitySourceAttribute, - ActivityAttribute, - EventAttribute, - ContextAttribute, - BaggageAttribute, - EscapeAttribute, - StatusDescriptionAttribute, - ]; - } - - public static class Logging - { - public static readonly TemplateInfo LoggerGenerationAttribute = TemplateInfo.Create( - TypeLibrary.Logging.LoggerGenerationAttribute - ); - public static readonly TemplateInfo LoggerAttribute = TemplateInfo.Create(TypeLibrary.Logging.LoggerAttribute); - public static readonly TemplateInfo LogAttribute = TemplateInfo.Create(TypeLibrary.Logging.LogAttribute); - public static readonly TemplateInfo LogPrefixType = TemplateInfo.Create(TypeLibrary.Logging.LogPrefixType); - public static readonly TemplateInfo LoggerGenerationMode = TemplateInfo.Create( - TypeLibrary.Logging.LoggerGenerationMode - ); - public static readonly TemplateInfo ExpandEnumerableAttribute = TemplateInfo.Create( - TypeLibrary.Logging.ExpandEnumerableAttribute - ); - public static readonly TemplateInfo TraceAttribute = TemplateInfo.Create(TypeLibrary.Logging.TraceAttribute); - public static readonly TemplateInfo DebugAttribute = TemplateInfo.Create(TypeLibrary.Logging.DebugAttribute); - public static readonly TemplateInfo InfoAttribute = TemplateInfo.Create(TypeLibrary.Logging.InfoAttribute); - public static readonly TemplateInfo WarningAttribute = TemplateInfo.Create( - TypeLibrary.Logging.WarningAttribute - ); - public static readonly TemplateInfo ErrorAttribute = TemplateInfo.Create(TypeLibrary.Logging.ErrorAttribute); - public static readonly TemplateInfo CriticalAttribute = TemplateInfo.Create( - TypeLibrary.Logging.CriticalAttribute - ); - - public static readonly TemplateInfo[] SpecificLogAttributes = - [ - TraceAttribute, - DebugAttribute, - InfoAttribute, - WarningAttribute, - ErrorAttribute, - CriticalAttribute, - ]; - - public static readonly Dictionary SpecificLogAttributesToLevel = new() - { - { TraceAttribute, 0 }, - { DebugAttribute, 1 }, - { InfoAttribute, 2 }, - { WarningAttribute, 3 }, - { ErrorAttribute, 4 }, - { CriticalAttribute, 5 }, - }; - - public static TemplateInfo[] GetTemplates() => - [ - LoggerGenerationAttribute, - LoggerAttribute, - LogAttribute, - LogPrefixType, - LoggerGenerationMode, - ExpandEnumerableAttribute, - TraceAttribute, - DebugAttribute, - InfoAttribute, - WarningAttribute, - ErrorAttribute, - CriticalAttribute, - ]; - } - - public static class Metrics - { - public static readonly TemplateInfo MeterGenerationAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.MeterGenerationAttribute - ); - public static readonly TemplateInfo MeterAttribute = TemplateInfo.Create(TypeLibrary.Metrics.MeterAttribute); - public static readonly TemplateInfo MeterNameGenerationType = TemplateInfo.Create( - TypeLibrary.Metrics.MeterNameGenerationType - ); - public static readonly TemplateInfo InstrumentMeasurementAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.InstrumentMeasurementAttribute - ); - public static readonly TemplateInfo AutoCounterAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.AutoCounterAttribute - ); - public static readonly TemplateInfo CounterAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.CounterAttribute - ); - public static readonly TemplateInfo UpDownCounterAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.UpDownCounterAttribute - ); - public static readonly TemplateInfo HistogramAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.HistogramAttribute - ); - public static readonly TemplateInfo ObservableCounterAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.ObservableCounterAttribute - ); - public static readonly TemplateInfo ObservableUpDownCounterAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.ObservableUpDownCounterAttribute - ); - public static readonly TemplateInfo ObservableGaugeAttribute = TemplateInfo.Create( - TypeLibrary.Metrics.ObservableGaugeAttribute - ); - - public static readonly TemplateInfo[] ValidInstrumentAttributes = - [ - AutoCounterAttribute, - CounterAttribute, - UpDownCounterAttribute, - HistogramAttribute, - ObservableCounterAttribute, - ObservableUpDownCounterAttribute, - ObservableGaugeAttribute, - ]; - - public static TemplateInfo[] GetTemplates() => - [ - MeterGenerationAttribute, - MeterAttribute, - MeterNameGenerationType, - InstrumentMeasurementAttribute, - AutoCounterAttribute, - CounterAttribute, - UpDownCounterAttribute, - HistogramAttribute, - ObservableCounterAttribute, - ObservableGaugeAttribute, - ObservableUpDownCounterAttribute, - ]; - } - - public static TemplateInfo[] GetAllTemplates() => - [.. Activities.GetTemplates(), .. Logging.GetTemplates(), .. Metrics.GetTemplates(), .. Shared.GetTemplates()]; -} diff --git a/src/src/SourceGenerator/Helpers/TypeLibrary.cs b/src/src/SourceGenerator/Helpers/TypeLibrary.cs index 99cb6eeb..29e33175 100644 --- a/src/src/SourceGenerator/Helpers/TypeLibrary.cs +++ b/src/src/SourceGenerator/Helpers/TypeLibrary.cs @@ -9,48 +9,47 @@ namespace Purview.Telemetry.SourceGenerator.Helpers; /// static class TypeLibrary { + public const string PurviewTelemetryNamespace = "Purview.Telemetry"; + public static class System { - public static readonly TypeIdentity Void = new("void", null); - public static readonly TypeIdentity String = PurviewTypeLibrary.System.String; - public static readonly TypeIdentity Object = PurviewTypeLibrary.System.Object; - public static readonly TypeIdentity Boolean = PurviewTypeLibrary.System.Boolean; - public static readonly TypeIdentity Int32 = PurviewTypeLibrary.System.Int32; - public static readonly TypeIdentity Int64 = PurviewTypeLibrary.System.Int64; public static readonly TypeIdentity Exception = TypeIdentity.Create(); public static readonly TypeIdentity IDisposable = TypeIdentity.Create(); public static readonly TypeIdentity DateTimeOffset = TypeIdentity.Create(); - public static readonly TypeIdentity Func = new(nameof(Func), "System"); - public static readonly TypeIdentity Action = new(nameof(Action), "System"); public static readonly TypeIdentity IEnumerable = TypeIdentity.Create(); public static readonly TypeIdentity GenericIEnumerable = new(typeof(IEnumerable<>)); public static readonly TypeIdentity List = new(typeof(List<>)); public static readonly TypeIdentity Dictionary = new(typeof(Dictionary<,>)); public static readonly TypeIdentity ConcurrentDictionary = new(typeof(ConcurrentDictionary<,>)); - public static readonly TypeIdentity TagList = new("TagList", "System.Diagnostics"); + public static readonly TypeIdentity TagList = new(nameof(TagList), "System.Diagnostics"); } public static class Activities { public static class SystemDiagnostics { - public static readonly TypeIdentity Activity = new("Activity", "System.Diagnostics"); - public static readonly TypeIdentity ActivitySource = new("ActivitySource", "System.Diagnostics"); - public static readonly TypeIdentity ActivityEvent = new("ActivityEvent", "System.Diagnostics"); - public static readonly TypeIdentity ActivityContext = new("ActivityContext", "System.Diagnostics"); - public static readonly TypeIdentity ActivityKind = new("ActivityKind", "System.Diagnostics"); - public static readonly TypeIdentity ActivityStatusCode = new("ActivityStatusCode", "System.Diagnostics"); + public const string DiagnosticsNamespace = "System.Diagnostics"; + + public static readonly TypeIdentity Activity = new(nameof(Activity), DiagnosticsNamespace); + public static readonly TypeIdentity ActivitySource = new(nameof(ActivitySource), DiagnosticsNamespace); + public static readonly TypeIdentity ActivityEvent = new(nameof(ActivityEvent), DiagnosticsNamespace); + public static readonly TypeIdentity ActivityContext = new(nameof(ActivityContext), DiagnosticsNamespace); + public static readonly TypeIdentity ActivityKind = new(nameof(ActivityKind), DiagnosticsNamespace); + public static readonly TypeIdentity ActivityStatusCode = new( + nameof(ActivityStatusCode), + DiagnosticsNamespace + ); public static readonly TypeIdentity ActivityTagsCollection = new( - "ActivityTagsCollection", - "System.Diagnostics" + nameof(ActivityTagsCollection), + DiagnosticsNamespace ); - public static readonly TypeIdentity ActivityLink = new("ActivityLink", "System.Diagnostics"); + public static readonly TypeIdentity ActivityLink = new(nameof(ActivityLink), DiagnosticsNamespace); public static readonly TypeReference ActivityTagIEnumerable = System.GenericIEnumerable.MakeGeneric( new TypeReference( new TypeIdentity("KeyValuePair", "System.Collections.Generic").MakeGeneric( - System.String.AsTypeReference(), - System.Object.AsTypeReference().Nullable() + PurviewTypeLibrary.System.String.AsTypeReference(), + PurviewTypeLibrary.System.Object.AsTypeReference() ) ) ); @@ -63,119 +62,208 @@ public static class SystemDiagnostics } public static readonly TypeIdentity ActivitySourceGenerationAttribute = new( - "ActivitySourceGenerationAttribute", - "Purview.Telemetry" + nameof(ActivitySourceGenerationAttribute), + PurviewTelemetryNamespace ); public static readonly TypeIdentity ActivitySourceAttribute = new( - "ActivitySourceAttribute", - "Purview.Telemetry" - ); - public static readonly TypeIdentity ActivityAttribute = new("ActivityAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity EventAttribute = new("EventAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity ContextAttribute = new("ContextAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity BaggageAttribute = new("BaggageAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity EscapeAttribute = new("EscapeAttribute", "Purview.Telemetry"); + nameof(ActivitySourceAttribute), + PurviewTelemetryNamespace + ); + public static readonly TypeIdentity ActivityAttribute = new( + nameof(ActivityAttribute), + PurviewTelemetryNamespace + ); + public static readonly TypeIdentity EventAttribute = new(nameof(EventAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity ContextAttribute = new(nameof(ContextAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity BaggageAttribute = new(nameof(BaggageAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity EscapeAttribute = new(nameof(EscapeAttribute), PurviewTelemetryNamespace); public static readonly TypeIdentity StatusDescriptionAttribute = new( - "StatusDescriptionAttribute", - "Purview.Telemetry" + nameof(StatusDescriptionAttribute), + PurviewTelemetryNamespace ); + + public static ImmutableArray GetGeneratedTypes() => + [ + ActivitySourceGenerationAttribute, + ActivitySourceAttribute, + ActivityAttribute, + EventAttribute, + ContextAttribute, + BaggageAttribute, + EscapeAttribute, + StatusDescriptionAttribute, + ]; } public static class Logging { public static class MicrosoftExtensions { - public static readonly TypeIdentity ILogger = new("ILogger", "Microsoft.Extensions.Logging"); - public static readonly TypeIdentity LoggerMessage = new("LoggerMessage", "Microsoft.Extensions.Logging"); - public static readonly TypeIdentity LogLevel = new("LogLevel", "Microsoft.Extensions.Logging"); - public static readonly TypeIdentity EventId = new("EventId", "Microsoft.Extensions.Logging"); + public const string LoggingNamespace = "Microsoft.Extensions.Logging"; + + public static readonly TypeIdentity ILogger = new(nameof(ILogger), LoggingNamespace); + public static readonly TypeIdentity LoggerMessage = new(nameof(LoggerMessage), LoggingNamespace); + public static readonly TypeIdentity LogLevel = new(nameof(LogLevel), LoggingNamespace); + public static readonly TypeIdentity EventId = new(nameof(EventId), LoggingNamespace); public static readonly TypeIdentity LoggerMessageHelper = new( - "LoggerMessageHelper", - "Microsoft.Extensions.Logging" + nameof(LoggerMessageHelper), + LoggingNamespace ); public static readonly TypeIdentity LogPropertiesAttribute = new( - "LogPropertiesAttribute", - "Microsoft.Extensions.Logging" + nameof(LogPropertiesAttribute), + LoggingNamespace ); public static readonly TypeIdentity LogPropertyIgnoreAttribute = new( - "LogPropertyIgnoreAttribute", - "Microsoft.Extensions.Logging" + nameof(LogPropertyIgnoreAttribute), + LoggingNamespace ); } public static readonly TypeIdentity LoggerGenerationAttribute = new( - "LoggerGenerationAttribute", - "Purview.Telemetry" + nameof(LoggerGenerationAttribute), + PurviewTelemetryNamespace + ); + public static readonly TypeIdentity LoggerAttribute = new(nameof(LoggerAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity LogAttribute = new(nameof(LogAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity LogPrefixType = new(nameof(LogPrefixType), PurviewTelemetryNamespace); + public static readonly TypeIdentity LoggerGenerationMode = new( + nameof(LoggerGenerationMode), + PurviewTelemetryNamespace ); - public static readonly TypeIdentity LoggerAttribute = new("LoggerAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity LogAttribute = new("LogAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity LogPrefixType = new("LogPrefixType", "Purview.Telemetry"); - public static readonly TypeIdentity LoggerGenerationMode = new("LoggerGenerationMode", "Purview.Telemetry"); public static readonly TypeIdentity ExpandEnumerableAttribute = new( - "ExpandEnumerableAttribute", - "Purview.Telemetry" - ); - public static readonly TypeIdentity TraceAttribute = new("TraceAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity DebugAttribute = new("DebugAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity InfoAttribute = new("InfoAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity WarningAttribute = new("WarningAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity ErrorAttribute = new("ErrorAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity CriticalAttribute = new("CriticalAttribute", "Purview.Telemetry"); + nameof(ExpandEnumerableAttribute), + PurviewTelemetryNamespace + ); + public static readonly TypeIdentity TraceAttribute = new(nameof(TraceAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity DebugAttribute = new(nameof(DebugAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity InfoAttribute = new(nameof(InfoAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity WarningAttribute = new(nameof(WarningAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity ErrorAttribute = new(nameof(ErrorAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity CriticalAttribute = new( + nameof(CriticalAttribute), + PurviewTelemetryNamespace + ); + + public static readonly ImmutableArray LogAttributeTargets = + [ + LogAttribute, + TraceAttribute, + DebugAttribute, + InfoAttribute, + WarningAttribute, + ErrorAttribute, + CriticalAttribute, + ]; + + public static ImmutableArray GetGeneratedTypes() => + [ + LoggerGenerationAttribute, + LoggerAttribute, + LogAttribute, + LogPrefixType, + LoggerGenerationMode, + ExpandEnumerableAttribute, + TraceAttribute, + DebugAttribute, + InfoAttribute, + WarningAttribute, + ErrorAttribute, + CriticalAttribute, + ]; + + public static ImmutableDictionary LogLevelMap = new Dictionary< + TypeIdentity, + LogLevelDetails + > + { + { TraceAttribute, new(TraceAttribute, 0, "Trace") }, + { DebugAttribute, new(DebugAttribute, 1, "Debug") }, + { InfoAttribute, new(InfoAttribute, 2, "Info") }, + { WarningAttribute, new(WarningAttribute, 3, "Warning") }, + { ErrorAttribute, new(ErrorAttribute, 4, "Error") }, + { CriticalAttribute, new(CriticalAttribute, 5, "Critical") }, + }.ToImmutableDictionary(); } public static class Metrics { public static class SystemDiagnostics { - public static readonly TypeIdentity Meter = new("Meter", "System.Diagnostics.Metrics"); - public static readonly TypeIdentity IMeterFactory = new("IMeterFactory", "System.Diagnostics.Metrics"); - public static readonly TypeIdentity MeterOptions = new("MeterOptions", "System.Diagnostics.Metrics"); - public static readonly TypeIdentity Measurement = new("Measurement", "System.Diagnostics.Metrics") + public const string SystemDiagnosticsMetricsNamespace = "System.Diagnostics.Metrics"; + + public static readonly TypeIdentity Meter = new(nameof(Meter), SystemDiagnosticsMetricsNamespace); + public static readonly TypeIdentity IMeterFactory = new( + nameof(IMeterFactory), + SystemDiagnosticsMetricsNamespace + ); + public static readonly TypeIdentity MeterOptions = new( + nameof(MeterOptions), + SystemDiagnosticsMetricsNamespace + ); + public static readonly TypeIdentity Measurement = new( + nameof(Measurement), + SystemDiagnosticsMetricsNamespace + ) { GenericArity = 1, }; - public static readonly TypeIdentity Counter = new("Counter", "System.Diagnostics.Metrics"); - public static readonly TypeIdentity UpDownCounter = new("UpDownCounter", "System.Diagnostics.Metrics"); - public static readonly TypeIdentity Histogram = new("Histogram", "System.Diagnostics.Metrics"); + public static readonly TypeIdentity Counter = new(nameof(Counter), SystemDiagnosticsMetricsNamespace); + public static readonly TypeIdentity UpDownCounter = new( + nameof(UpDownCounter), + SystemDiagnosticsMetricsNamespace + ); + public static readonly TypeIdentity Histogram = new(nameof(Histogram), SystemDiagnosticsMetricsNamespace); public static readonly TypeIdentity ObservableCounter = new( - "ObservableCounter", - "System.Diagnostics.Metrics" + nameof(ObservableCounter), + SystemDiagnosticsMetricsNamespace + ); + public static readonly TypeIdentity ObservableGauge = new( + nameof(ObservableGauge), + SystemDiagnosticsMetricsNamespace ); - public static readonly TypeIdentity ObservableGauge = new("ObservableGauge", "System.Diagnostics.Metrics"); public static readonly TypeIdentity ObservableUpDownCounter = new( - "ObservableUpDownCounter", - "System.Diagnostics.Metrics" + nameof(ObservableUpDownCounter), + SystemDiagnosticsMetricsNamespace ); } public static readonly TypeIdentity MeterGenerationAttribute = new( - "MeterGenerationAttribute", - "Purview.Telemetry" + nameof(MeterGenerationAttribute), + PurviewTelemetryNamespace ); - public static readonly TypeIdentity MeterAttribute = new("MeterAttribute", "Purview.Telemetry"); + public static readonly TypeIdentity MeterAttribute = new(nameof(MeterAttribute), PurviewTelemetryNamespace); public static readonly TypeIdentity MeterNameGenerationType = new( - "MeterNameGenerationType", - "Purview.Telemetry" + nameof(MeterNameGenerationType), + PurviewTelemetryNamespace ); public static readonly TypeIdentity InstrumentMeasurementAttribute = new( - "InstrumentMeasurementAttribute", - "Purview.Telemetry" + nameof(InstrumentMeasurementAttribute), + PurviewTelemetryNamespace + ); + public static readonly TypeIdentity AutoCounterAttribute = new( + nameof(AutoCounterAttribute), + PurviewTelemetryNamespace + ); + public static readonly TypeIdentity CounterAttribute = new(nameof(CounterAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity UpDownCounterAttribute = new( + nameof(UpDownCounterAttribute), + PurviewTelemetryNamespace + ); + public static readonly TypeIdentity HistogramAttribute = new( + nameof(HistogramAttribute), + PurviewTelemetryNamespace ); - public static readonly TypeIdentity AutoCounterAttribute = new("AutoCounterAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity CounterAttribute = new("CounterAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity UpDownCounterAttribute = new("UpDownCounterAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity HistogramAttribute = new("HistogramAttribute", "Purview.Telemetry"); public static readonly TypeIdentity ObservableCounterAttribute = new( - "ObservableCounterAttribute", - "Purview.Telemetry" + nameof(ObservableCounterAttribute), + PurviewTelemetryNamespace ); public static readonly TypeIdentity ObservableUpDownCounterAttribute = new( - "ObservableUpDownCounterAttribute", - "Purview.Telemetry" + nameof(ObservableUpDownCounterAttribute), + PurviewTelemetryNamespace ); public static readonly TypeIdentity ObservableGaugeAttribute = new( - "ObservableGaugeAttribute", - "Purview.Telemetry" + nameof(ObservableGaugeAttribute), + PurviewTelemetryNamespace ); public static readonly ImmutableDictionary InstrumentTypeMap = new Dictionary< @@ -190,37 +278,75 @@ public static class SystemDiagnostics { InstrumentTypes.ObservableGauge, SystemDiagnostics.ObservableGauge }, { InstrumentTypes.ObservableUpDownCounter, SystemDiagnostics.ObservableUpDownCounter }, }.ToImmutableDictionary(); + + public static ImmutableArray GetGeneratedTypes() => + [ + MeterGenerationAttribute, + MeterAttribute, + MeterNameGenerationType, + InstrumentMeasurementAttribute, + AutoCounterAttribute, + CounterAttribute, + UpDownCounterAttribute, + HistogramAttribute, + ObservableCounterAttribute, + ObservableUpDownCounterAttribute, + ObservableGaugeAttribute, + ]; } public static class DependencyInjection { + public const string DependencyInjectionNamespace = "Microsoft.Extensions.DependencyInjection"; + public static readonly TypeIdentity IServiceCollection = new( - "IServiceCollection", - "Microsoft.Extensions.DependencyInjection" + nameof(IServiceCollection), + DependencyInjectionNamespace ); public static readonly TypeIdentity ServiceDescriptor = new( - "ServiceDescriptor", - "Microsoft.Extensions.DependencyInjection" + nameof(ServiceDescriptor), + DependencyInjectionNamespace ); public static readonly TypeIdentity ServiceLifetime = new( - "ServiceLifetime", - "Microsoft.Extensions.DependencyInjection" + nameof(ServiceLifetime), + DependencyInjectionNamespace ); } - public static class Telemetry + public static class TelemetryShared { - public static readonly TypeIdentity TagAttribute = new("TagAttribute", "Purview.Telemetry"); - public static readonly TypeIdentity ExcludeAttribute = new("ExcludeAttribute", "Purview.Telemetry"); + public static readonly TypeIdentity TagAttribute = new(nameof(TagAttribute), PurviewTelemetryNamespace); + public static readonly TypeIdentity ExcludeAttribute = new(nameof(ExcludeAttribute), PurviewTelemetryNamespace); public static readonly TypeIdentity TelemetryGenerationAttribute = new( - "TelemetryGenerationAttribute", - "Purview.Telemetry" + nameof(TelemetryGenerationAttribute), + PurviewTelemetryNamespace + ); + public static readonly TypeIdentity TargetsEnum = new(nameof(TargetsEnum), PurviewTelemetryNamespace); + public static readonly TypeIdentity NamingConventionEnum = new( + nameof(NamingConventionEnum), + PurviewTelemetryNamespace ); - public static readonly TypeIdentity TargetsEnum = new("Targets", "Purview.Telemetry"); - public static readonly TypeIdentity NamingConventionEnum = new("NamingConvention", "Purview.Telemetry"); public static readonly TypeIdentity ExcludeTargetsAttribute = new( - "ExcludeTargetsAttribute", - "Purview.Telemetry" + nameof(ExcludeTargetsAttribute), + PurviewTelemetryNamespace ); + + public static ImmutableArray GetGeneratedTypes() => + [ + TagAttribute, + ExcludeAttribute, + TelemetryGenerationAttribute, + TargetsEnum, + NamingConventionEnum, + ExcludeTargetsAttribute, + ]; } + + public static ImmutableArray GetAllGeneratedTypes() => + [ + .. Activities.GetGeneratedTypes(), + .. Logging.GetGeneratedTypes(), + .. Metrics.GetGeneratedTypes(), + .. TelemetryShared.GetGeneratedTypes(), + ]; } diff --git a/src/src/SourceGenerator/Helpers/Utilities.cs b/src/src/SourceGenerator/Helpers/Utilities.cs index 0b5b1a71..7135ad90 100644 --- a/src/src/SourceGenerator/Helpers/Utilities.cs +++ b/src/src/SourceGenerator/Helpers/Utilities.cs @@ -3,7 +3,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Purview.Telemetry.SourceGenerator.Records; -using Purview.Telemetry.SourceGenerator.Templates; namespace Purview.Telemetry.SourceGenerator.Helpers; @@ -91,27 +90,27 @@ GenerationType requestedType } static bool IsActivityAttribute(TypeReference attributeType) => - TemplateLibrary.Activities.ActivityAttribute == attributeType - || TemplateLibrary.Activities.EventAttribute == attributeType - || TemplateLibrary.Activities.ContextAttribute == attributeType; + TypeLibrary.Activities.ActivityAttribute == attributeType + || TypeLibrary.Activities.EventAttribute == attributeType + || TypeLibrary.Activities.ContextAttribute == attributeType; static bool IsLoggingAttribute(TypeReference attributeType) => - TemplateLibrary.Logging.LogAttribute == attributeType - || TemplateLibrary.Logging.TraceAttribute == attributeType - || TemplateLibrary.Logging.DebugAttribute == attributeType - || TemplateLibrary.Logging.InfoAttribute == attributeType - || TemplateLibrary.Logging.WarningAttribute == attributeType - || TemplateLibrary.Logging.ErrorAttribute == attributeType - || TemplateLibrary.Logging.CriticalAttribute == attributeType; + TypeLibrary.Logging.LogAttribute == attributeType + || TypeLibrary.Logging.TraceAttribute == attributeType + || TypeLibrary.Logging.DebugAttribute == attributeType + || TypeLibrary.Logging.InfoAttribute == attributeType + || TypeLibrary.Logging.WarningAttribute == attributeType + || TypeLibrary.Logging.ErrorAttribute == attributeType + || TypeLibrary.Logging.CriticalAttribute == attributeType; static bool IsMetricsAttribute(TypeReference attributeType) => - TemplateLibrary.Metrics.CounterAttribute == attributeType - || TemplateLibrary.Metrics.AutoCounterAttribute == attributeType - || TemplateLibrary.Metrics.UpDownCounterAttribute == attributeType - || TemplateLibrary.Metrics.HistogramAttribute == attributeType - || TemplateLibrary.Metrics.ObservableCounterAttribute == attributeType - || TemplateLibrary.Metrics.ObservableGaugeAttribute == attributeType - || TemplateLibrary.Metrics.ObservableUpDownCounterAttribute == attributeType; + TypeLibrary.Metrics.CounterAttribute == attributeType + || TypeLibrary.Metrics.AutoCounterAttribute == attributeType + || TypeLibrary.Metrics.UpDownCounterAttribute == attributeType + || TypeLibrary.Metrics.HistogramAttribute == attributeType + || TypeLibrary.Metrics.ObservableCounterAttribute == attributeType + || TypeLibrary.Metrics.ObservableGaugeAttribute == attributeType + || TypeLibrary.Metrics.ObservableUpDownCounterAttribute == attributeType; static int CountFlags(GenerationType type) { @@ -308,19 +307,15 @@ public static bool IsExceptionType(this ITypeSymbol typeSymbol) public static bool ContainsAttribute(ISymbol symbol, TypeIdentity type, CancellationToken token) => TryContainsAttribute(symbol, type, token, out _); - public static bool ContainsAttribute(ISymbol symbol, TemplateInfo templateInfo, CancellationToken token) => - TryContainsAttribute(symbol, templateInfo, token, out _); - - public static bool ContainsAttribute(ISymbol symbol, TemplateInfo[] templateInfo, CancellationToken token) => - TryContainsAttribute(symbol, templateInfo, token, out _, out _); - public static bool TryContainsAttribute( ISymbol symbol, - TypeIdentity type, + IEnumerable types, CancellationToken token, + out TypeIdentity matchingType, out AttributeData? attributeData ) { + matchingType = default; attributeData = null; var attributes = symbol.GetAttributes(); @@ -333,10 +328,15 @@ out AttributeData? attributeData if (!TypeIdentity.TryCreate(attribute.AttributeClass, out var attributeType)) continue; - if (attributeType.Equals(type)) + foreach (var type in types) { - attributeData = attribute; - return true; + if (type.Equals(attributeType)) + { + matchingType = type; + attributeData = attribute; + + return true; + } } } @@ -345,7 +345,7 @@ out AttributeData? attributeData public static bool TryContainsAttribute( ISymbol symbol, - TemplateInfo templateInfo, + TypeIdentity type, CancellationToken token, out AttributeData? attributeData ) @@ -362,7 +362,7 @@ out AttributeData? attributeData if (!TypeIdentity.TryCreate(attribute.AttributeClass, out var attributeType)) continue; - if (templateInfo.Equals(attributeType)) + if (attributeType.Equals(type)) { attributeData = attribute; return true; @@ -372,43 +372,6 @@ out AttributeData? attributeData return false; } - public static bool TryContainsAttribute( - ISymbol symbol, - TemplateInfo[] templateInfo, - CancellationToken token, - out AttributeData? attributeData, - out TemplateInfo? matchingTemplate - ) - { - attributeData = null; - matchingTemplate = null; - - var attributes = symbol.GetAttributes(); - foreach (var attribute in attributes) - { - token.ThrowIfCancellationRequested(); - - if (attribute.AttributeClass == null) - continue; - - if (!TypeIdentity.TryCreate(attribute.AttributeClass, out var attributeType)) - continue; - - foreach (var template in templateInfo) - { - if (template.Equals(attributeType)) - { - attributeData = attribute; - matchingTemplate = template; - - return true; - } - } - } - - return false; - } - public static string LowercaseFirstChar(string value) { if (value.Length > 0) @@ -509,12 +472,12 @@ public static string GenerateInstrumentPrefixFromInterfaceName(string interfaceN /// public static bool HasMetricsAttribute(IMethodSymbol method, CancellationToken token) { - return ContainsAttribute(method, TemplateLibrary.Metrics.CounterAttribute, token) - || ContainsAttribute(method, TemplateLibrary.Metrics.AutoCounterAttribute, token) - || ContainsAttribute(method, TemplateLibrary.Metrics.UpDownCounterAttribute, token) - || ContainsAttribute(method, TemplateLibrary.Metrics.HistogramAttribute, token) - || ContainsAttribute(method, TemplateLibrary.Metrics.ObservableCounterAttribute, token) - || ContainsAttribute(method, TemplateLibrary.Metrics.ObservableGaugeAttribute, token) - || ContainsAttribute(method, TemplateLibrary.Metrics.ObservableUpDownCounterAttribute, token); + return ContainsAttribute(method, TypeLibrary.Metrics.CounterAttribute, token) + || ContainsAttribute(method, TypeLibrary.Metrics.AutoCounterAttribute, token) + || ContainsAttribute(method, TypeLibrary.Metrics.UpDownCounterAttribute, token) + || ContainsAttribute(method, TypeLibrary.Metrics.HistogramAttribute, token) + || ContainsAttribute(method, TypeLibrary.Metrics.ObservableCounterAttribute, token) + || ContainsAttribute(method, TypeLibrary.Metrics.ObservableGaugeAttribute, token) + || ContainsAttribute(method, TypeLibrary.Metrics.ObservableUpDownCounterAttribute, token); } } diff --git a/src/src/SourceGenerator/Records/ActivityRecords.cs b/src/src/SourceGenerator/Records/ActivityRecords.cs index a38d1eb5..f4eb5dd0 100644 --- a/src/src/SourceGenerator/Records/ActivityRecords.cs +++ b/src/src/SourceGenerator/Records/ActivityRecords.cs @@ -4,16 +4,42 @@ sealed record ActivitySourceTarget( TelemetryGenerationAttributeData TelemetryGeneration, GenerationType GenerationType, string ClassNameToGenerate, - string? ClassNamespace, EquatableArray ParentClasses, - string? FullNamespace, - string? FullyQualifiedName, TypeReference InterfaceType, ActivitySourceGenerationAttributeData? ActivitySourceGenerationAttribute, string? ActivitySourceName, EquatableArray ActivityMethods, ActivitySourceAttributeData ActivityTargetAttributeRecord -); +) +{ + public string? ClassNamespace => TelemetryGeneration.TelemetryNamesNamespace ?? InterfaceType.Identity.Namespace; + + public string? FullNamespace + { + get + { + var telemetryNamesNamespace = TelemetryGeneration.TelemetryNamesNamespace; + if (telemetryNamesNamespace != null) + return telemetryNamesNamespace + "."; + + var ns = InterfaceType.Identity.Namespace; + if (ns == null && ParentClasses.IsEmpty) + return null; + + var builder = new System.Text.StringBuilder(); + if (ns != null) + builder.Append(ns).Append('.'); + + for (var i = ParentClasses.Count - 1; i >= 0; i--) + builder.Append(ParentClasses[i]).Append('.'); + + return builder.ToString(); + } + } + + public string? FullyQualifiedName => + FullNamespace is null ? ClassNameToGenerate : FullNamespace + ClassNameToGenerate; +} sealed record ActivityBasedGenerationTarget( string MethodName, diff --git a/src/src/SourceGenerator/Records/AttributeDataModels.cs b/src/src/SourceGenerator/Records/AttributeDataModels.cs index b63a0fd8..774edc6c 100644 --- a/src/src/SourceGenerator/Records/AttributeDataModels.cs +++ b/src/src/SourceGenerator/Records/AttributeDataModels.cs @@ -1,3 +1,5 @@ +using Purview.Telemetry.SourceGenerator.Helpers; + namespace Purview.Telemetry.SourceGenerator.Records; #pragma warning disable format @@ -54,7 +56,8 @@ readonly partial record struct TelemetryGenerationAttributeData( [Property(DefaultValue = false)] bool DependencyInjectionClassIsPublic, [Property(DefaultValue = 1)] int NamingConvention, [Property(DefaultValue = true)] bool GenerateTelemetryNamesClass, - [Property] string? TelemetryNamesClassName + [Property] string? TelemetryNamesClassName, + [Property] string? TelemetryNamesNamespace ); [Generate("Purview.Telemetry.TagAttribute")] @@ -104,52 +107,74 @@ readonly partial record struct ExpandEnumerableAttributeData( ); [Generate("Purview.Telemetry.TraceAttribute")] -readonly partial record struct TraceAttributeData( - [Argument("messageTemplate")] [Property] string? MessageTemplate, - [Argument("eventId")] [Property(DefaultValue = -1)] int EventId, - [Argument("name")] [Property] string? Name, - [Property(DefaultValue = 0)] int GenerationMode -); +readonly partial record struct TraceAttributeData([NestedModel] LogAttributeData Log) +{ + public LogAttributeData ToLogAttribute() => + Log with + { + Level = TypeLibrary.Logging.LogLevelMap[TypeLibrary.Logging.TraceAttribute].Value, + MessageTemplate = SharedHelpers.NullIfWhitespace(Log.MessageTemplate), + Name = SharedHelpers.NullIfWhitespace(Log.Name), + }; +} [Generate("Purview.Telemetry.DebugAttribute")] -readonly partial record struct DebugAttributeData( - [Argument("messageTemplate")] [Property] string? MessageTemplate, - [Argument("eventId")] [Property(DefaultValue = -1)] int EventId, - [Argument("name")] [Property] string? Name, - [Property(DefaultValue = 0)] int GenerationMode -); +readonly partial record struct DebugAttributeData([NestedModel] LogAttributeData Log) +{ + public LogAttributeData ToLogAttribute() => + Log with + { + Level = TypeLibrary.Logging.LogLevelMap[TypeLibrary.Logging.DebugAttribute].Value, + MessageTemplate = SharedHelpers.NullIfWhitespace(Log.MessageTemplate), + Name = SharedHelpers.NullIfWhitespace(Log.Name), + }; +} [Generate("Purview.Telemetry.InfoAttribute")] -readonly partial record struct InfoAttributeData( - [Argument("messageTemplate")] [Property] string? MessageTemplate, - [Argument("eventId")] [Property(DefaultValue = -1)] int EventId, - [Argument("name")] [Property] string? Name, - [Property(DefaultValue = 0)] int GenerationMode -); +readonly partial record struct InfoAttributeData([NestedModel] LogAttributeData Log) +{ + public LogAttributeData ToLogAttribute() => + Log with + { + Level = TypeLibrary.Logging.LogLevelMap[TypeLibrary.Logging.InfoAttribute].Value, + MessageTemplate = SharedHelpers.NullIfWhitespace(Log.MessageTemplate), + Name = SharedHelpers.NullIfWhitespace(Log.Name), + }; +} [Generate("Purview.Telemetry.WarningAttribute")] -readonly partial record struct WarningAttributeData( - [Argument("messageTemplate")] [Property] string? MessageTemplate, - [Argument("eventId")] [Property(DefaultValue = -1)] int EventId, - [Argument("name")] [Property] string? Name, - [Property(DefaultValue = 0)] int GenerationMode -); +readonly partial record struct WarningAttributeData([NestedModel] LogAttributeData Log) +{ + public LogAttributeData ToLogAttribute() => + Log with + { + Level = TypeLibrary.Logging.LogLevelMap[TypeLibrary.Logging.WarningAttribute].Value, + MessageTemplate = SharedHelpers.NullIfWhitespace(Log.MessageTemplate), + Name = SharedHelpers.NullIfWhitespace(Log.Name), + }; +} [Generate("Purview.Telemetry.ErrorAttribute")] -readonly partial record struct ErrorAttributeData( - [Argument("messageTemplate")] [Property] string? MessageTemplate, - [Argument("eventId")] [Property(DefaultValue = -1)] int EventId, - [Argument("name")] [Property] string? Name, - [Property(DefaultValue = 0)] int GenerationMode -); +readonly partial record struct ErrorAttributeData([NestedModel] LogAttributeData Log) +{ + public LogAttributeData ToLogAttribute() => + Log with + { + Level = TypeLibrary.Logging.LogLevelMap[TypeLibrary.Logging.ErrorAttribute].Value, + }; +} [Generate("Purview.Telemetry.CriticalAttribute")] -readonly partial record struct CriticalAttributeData( - [Argument("messageTemplate")] [Property] string? MessageTemplate, - [Argument("eventId")] [Property(DefaultValue = -1)] int EventId, - [Argument("name")] [Property] string? Name, - [Property(DefaultValue = 0)] int GenerationMode -); +readonly partial record struct CriticalAttributeData([NestedModel] LogAttributeData Log) +{ + public LogAttributeData ToLogAttribute() => + Log with + { + Level = TypeLibrary.Logging.LogLevelMap[TypeLibrary.Logging.CriticalAttribute].Value, + MessageTemplate = SharedHelpers.NullIfWhitespace(Log.MessageTemplate), + Name = SharedHelpers.NullIfWhitespace(Log.Name), + }; +} [Generate("Microsoft.Extensions.Logging.LogPropertiesAttribute")] readonly partial record struct LogPropertiesAttributeData( diff --git a/src/src/SourceGenerator/Records/GenericAttributeRecords.cs b/src/src/SourceGenerator/Records/GenericAttributeRecords.cs index 4eccc47c..545dd177 100644 --- a/src/src/SourceGenerator/Records/GenericAttributeRecords.cs +++ b/src/src/SourceGenerator/Records/GenericAttributeRecords.cs @@ -3,10 +3,10 @@ namespace Purview.Telemetry.SourceGenerator.Records; /// /// A parameter that carries either a [Tag] or [Baggage] marker attribute. /// -sealed record TagOrBaggageAttributeRecord(string? Name, bool SkipOnNullOrEmpty); +readonly record struct TagOrBaggageAttributeRecord(string? Name, bool SkipOnNullOrEmpty); /// -/// sealed record for ExcludeTargetsAttribute on parameters. +/// readonly record struct for ExcludeTargetsAttribute on parameters. /// Tracks which target families a parameter should be excluded from. /// -sealed record ExcludeTargetsAttributeRecord(GenerationType ExcludedTargets); +readonly record struct ExcludeTargetsAttributeRecord(GenerationType ExcludedTargets); diff --git a/src/src/SourceGenerator/Records/GenericRecords.cs b/src/src/SourceGenerator/Records/GenericRecords.cs index f37cde3f..3435f82b 100644 --- a/src/src/SourceGenerator/Records/GenericRecords.cs +++ b/src/src/SourceGenerator/Records/GenericRecords.cs @@ -14,7 +14,7 @@ enum GenerationType All = Activities | Logging | Metrics, } -sealed record TargetGeneration( +readonly record struct TargetGeneration( bool IsValid, bool RaiseInferenceNotSupportedWithMultiTargeting, bool RaiseMultiGenerationTargetsNotSupported, @@ -24,7 +24,7 @@ sealed record TargetGeneration( bool RaiseMissingInterfaceSource = false ); -public record struct MessageTemplateHole( +public readonly record struct MessageTemplateHole( string? Name, int? Ordinal, int? Alignment, diff --git a/src/src/SourceGenerator/Records/LoggerRecords.cs b/src/src/SourceGenerator/Records/LoggerRecords.cs index 961ebe23..d940df73 100644 --- a/src/src/SourceGenerator/Records/LoggerRecords.cs +++ b/src/src/SourceGenerator/Records/LoggerRecords.cs @@ -4,16 +4,42 @@ sealed record LoggerTarget( TelemetryGenerationAttributeData TelemetryGeneration, GenerationType GenerationType, string ClassNameToGenerate, - string? ClassNamespace, EquatableArray ParentClasses, - string? FullNamespace, - string FullyQualifiedName, TypeReference InterfaceType, LoggerAttributeData LoggerAttribute, int DefaultLevel, EquatableArray LogMethods, bool UseMSLoggingTelemetryBasedGeneration -); +) +{ + public string? ClassNamespace => TelemetryGeneration.TelemetryNamesNamespace ?? InterfaceType.Identity.Namespace; + + public string? FullNamespace + { + get + { + var telemetryNamesNamespace = TelemetryGeneration.TelemetryNamesNamespace; + if (telemetryNamesNamespace != null) + return telemetryNamesNamespace + "."; + + var ns = InterfaceType.Identity.Namespace; + if (ns == null && ParentClasses.IsEmpty) + return null; + + var builder = new System.Text.StringBuilder(); + if (ns != null) + builder.Append(ns).Append('.'); + + for (var i = ParentClasses.Count - 1; i >= 0; i--) + builder.Append(ParentClasses[i]).Append('.'); + + return builder.ToString(); + } + } + + public string FullyQualifiedName => + FullNamespace is null ? ClassNameToGenerate : FullNamespace + ClassNameToGenerate; +} sealed record LogMethodTarget( string MethodName, @@ -62,4 +88,6 @@ sealed record LogParameterTarget( public bool UsedInTemplate => !ReferencedHoles.IsEmpty; } -sealed record LogPropertiesParameterDetails(string PropertyName, bool IsNullable); +readonly record struct LogPropertiesParameterDetails(string PropertyName, bool IsNullable); + +readonly record struct LogLevelDetails(TypeIdentity LevelType, int Value, string Name); diff --git a/src/src/SourceGenerator/Records/MetricRecords.cs b/src/src/SourceGenerator/Records/MetricRecords.cs index 9ebd9147..5529f7ba 100644 --- a/src/src/SourceGenerator/Records/MetricRecords.cs +++ b/src/src/SourceGenerator/Records/MetricRecords.cs @@ -4,15 +4,41 @@ sealed record MeterTarget( TelemetryGenerationAttributeData TelemetryGeneration, GenerationType GenerationType, string ClassNameToGenerate, - string? ClassNamespace, EquatableArray ParentClasses, - string? FullNamespace, - string? FullyQualifiedName, TypeReference InterfaceType, string? MeterName, MeterGenerationAttributeData? MeterGeneration, EquatableArray InstrumentationMethods -); +) +{ + public string? ClassNamespace => TelemetryGeneration.TelemetryNamesNamespace ?? InterfaceType.Identity.Namespace; + + public string? FullNamespace + { + get + { + var telemetryNamesNamespace = TelemetryGeneration.TelemetryNamesNamespace; + if (telemetryNamesNamespace != null) + return telemetryNamesNamespace + "."; + + var ns = InterfaceType.Identity.Namespace; + if (ns == null && ParentClasses.IsEmpty) + return null; + + var builder = new System.Text.StringBuilder(); + if (ns != null) + builder.Append(ns).Append('.'); + + for (var i = ParentClasses.Count - 1; i >= 0; i--) + builder.Append(ParentClasses[i]).Append('.'); + + return builder.ToString(); + } + } + + public string? FullyQualifiedName => + FullNamespace is null ? ClassNameToGenerate : FullNamespace + ClassNameToGenerate; +} sealed record InstrumentTarget( string MethodName, diff --git a/src/src/SourceGenerator/Records/OutputContexts.cs b/src/src/SourceGenerator/Records/OutputContexts.cs index 080c010d..c70c148b 100644 --- a/src/src/SourceGenerator/Records/OutputContexts.cs +++ b/src/src/SourceGenerator/Records/OutputContexts.cs @@ -8,15 +8,27 @@ namespace Purview.Telemetry.SourceGenerator.Records; readonly record struct ActivityOutputContext( ActivitySourceTarget Target, GenerationContext Context -); +) +{ + public CodeWriter CreateWriter() => Context.CreateCodeWriter().WriteAutoGeneratedHeader(); +} -readonly record struct LoggerOutputContext(LoggerTarget Target, GenerationContext Context); +readonly record struct LoggerOutputContext(LoggerTarget Target, GenerationContext Context) +{ + public CodeWriter CreateWriter() => Context.CreateCodeWriter().WriteAutoGeneratedHeader(); +} -readonly record struct MeterOutputContext(MeterTarget Target, GenerationContext Context); +readonly record struct MeterOutputContext(MeterTarget Target, GenerationContext Context) +{ + public CodeWriter CreateWriter() => Context.CreateCodeWriter().WriteAutoGeneratedHeader(); +} readonly record struct TelemetryNamesOutputContext( string AssemblyName, EquatableArray> MeterTargets, EquatableArray> ActivityTargets, GenerationContext Context -); +) +{ + public CodeWriter CreateWriter() => Context.CreateCodeWriter().WriteAutoGeneratedHeader(); +} diff --git a/src/src/SourceGenerator/Records/TelemetryCapabilities.cs b/src/src/SourceGenerator/Records/TelemetryCapabilities.cs index 66ed0299..6f0300c7 100644 --- a/src/src/SourceGenerator/Records/TelemetryCapabilities.cs +++ b/src/src/SourceGenerator/Records/TelemetryCapabilities.cs @@ -3,5 +3,7 @@ namespace Purview.Telemetry.SourceGenerator.Records; /// /// Capabilities detected for the current compilation. /// -public sealed record TelemetryCapabilities(bool SupportsNullableAnnotations, bool SupportsIMeterFactory) - : IGenerationCapabilities; +public sealed record TelemetryCapabilities( + //bool SupportsNullableAnnotations, + bool SupportsIMeterFactory +) : IGenerationCapabilities; diff --git a/src/src/SourceGenerator/Records/TelemetryDiagnosticDescriptor.cs b/src/src/SourceGenerator/Records/TelemetryDiagnosticDescriptor.cs deleted file mode 100644 index cfe57a9b..00000000 --- a/src/src/SourceGenerator/Records/TelemetryDiagnosticDescriptor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.CodeAnalysis; - -namespace Purview.Telemetry.SourceGenerator.Records; - -sealed record TelemetryDiagnosticDescriptor( - string Id, - string Title, - string Description, - DiagnosticSeverity Severity, - string Category, - bool EnabledByDefault = true -); diff --git a/src/src/SourceGenerator/Records/TelemetryRules.Activities.cs b/src/src/SourceGenerator/Records/TelemetryRules.Activities.cs index 89ef13b1..19905e6c 100644 --- a/src/src/SourceGenerator/Records/TelemetryRules.Activities.cs +++ b/src/src/SourceGenerator/Records/TelemetryRules.Activities.cs @@ -23,7 +23,7 @@ CancellationToken token if (string.IsNullOrWhiteSpace(target.ActivitySourceName)) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.NoActivitySourceSpecified), + DiagnosticLibrary.Activities.NoActivitySourceSpecified.Descriptor, interfaceSymbol ) ); @@ -36,10 +36,7 @@ CancellationToken token && validMethods.Any(static m => m.MethodType != ActivityMethodType.Activity) ) diagnostics.Add( - DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.NoActivityMethodsDefined), - interfaceSymbol - ) + DiagnosticInfo.Create(DiagnosticLibrary.Activities.NoActivityMethodsDefined.Descriptor, interfaceSymbol) ); var generateDiagnosticsForMissingActivity = @@ -83,15 +80,12 @@ CancellationToken token var returnsActivity = method.ReturnType.Identity.Equals(TypeLibrary.Activities.SystemDiagnostics.Activity); if (!returnsActivity) diagnostics.Add( - DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.DoesNotReturnActivity), - methodSymbol - ) + DiagnosticInfo.Create(DiagnosticLibrary.Activities.DoesNotReturnActivity.Descriptor, methodSymbol) ); else if (!method.ReturnType.IsNullable) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.ActivityReturnTypeShouldBeNullable), + DiagnosticLibrary.Activities.ActivityReturnTypeShouldBeNullable.Descriptor, methodSymbol ) ); @@ -99,7 +93,7 @@ CancellationToken token if (!isValidReturnType) diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Activities.InvalidReturnType), methodSymbol) + DiagnosticInfo.Create(DiagnosticLibrary.Activities.InvalidReturnType.Descriptor, methodSymbol) ); // TSG3014/TSG3015: best-practice diagnostics for missing/misplaced Activity parameters, @@ -109,7 +103,7 @@ CancellationToken token if (!method.HasActivityParameter) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.DoesNotAcceptActivityParameter), + DiagnosticLibrary.Activities.DoesNotAcceptActivityParameter.Descriptor, methodSymbol ) ); @@ -120,7 +114,7 @@ CancellationToken token if (method.Parameters[0].ParamDestination != ActivityParameterDestination.Activity) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.ActivityShouldBeTheFirstParameter), + DiagnosticLibrary.Activities.ActivityShouldBeTheFirstParameter.Descriptor, methodSymbol ) ); @@ -133,7 +127,7 @@ CancellationToken token if (recordsException && !string.Equals(method.ActivityOrEventName, "exception", StringComparison.Ordinal)) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.ExceptionEventNotStandardName), + DiagnosticLibrary.Activities.ExceptionEventNotStandardName.Descriptor, methodSymbol, method.ActivityOrEventName ) @@ -165,7 +159,7 @@ CancellationToken token { diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.BaggageParameterShouldBeString), + DiagnosticLibrary.Activities.BaggageParameterShouldBeString.Descriptor, GetParameterLocation(methodSymbol, baggage.ParameterName) ) ); @@ -193,7 +187,7 @@ p.ParamDestination is not (ActivityParameterDestination.Tag or ActivityParameter diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.DuplicateParameterTypes), + DiagnosticLibrary.Activities.DuplicateParameterTypes.Descriptor, GetParameterLocation(methodSymbol, secondParameter.ParameterName), names, group.Key.ToString() @@ -222,7 +216,7 @@ CancellationToken token case ActivityParameterDestination.Activity when method.MethodType == ActivityMethodType.Activity: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.ActivityParameterNotAllowed), + DiagnosticLibrary.Activities.ActivityParameterNotAllowed.Descriptor, location, parameterName ) @@ -231,7 +225,7 @@ CancellationToken token case ActivityParameterDestination.Timestamp when method.MethodType != ActivityMethodType.Event: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.TimestampParameterNotAllowed), + DiagnosticLibrary.Activities.TimestampParameterNotAllowed.Descriptor, location, parameterName ) @@ -240,7 +234,7 @@ CancellationToken token case ActivityParameterDestination.StartTime when method.MethodType != ActivityMethodType.Activity: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.StartTimeParameterNotAllowed), + DiagnosticLibrary.Activities.StartTimeParameterNotAllowed.Descriptor, location, parameterName ) @@ -250,7 +244,7 @@ CancellationToken token when method.MethodType != ActivityMethodType.Activity: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.ParentContextOrIdParameterNotAllowed), + DiagnosticLibrary.Activities.ParentContextOrIdParameterNotAllowed.Descriptor, location, parameterName ) @@ -259,7 +253,7 @@ CancellationToken token case ActivityParameterDestination.LinksEnumerable when method.MethodType != ActivityMethodType.Activity: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.LinksParameterNotAllowed), + DiagnosticLibrary.Activities.LinksParameterNotAllowed.Descriptor, location, parameterName ) @@ -268,7 +262,7 @@ CancellationToken token case ActivityParameterDestination.TagsEnumerable when method.MethodType == ActivityMethodType.Context: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.TagsParameterNotAllowed), + DiagnosticLibrary.Activities.TagsParameterNotAllowed.Descriptor, location, parameterName ) @@ -278,7 +272,7 @@ CancellationToken token when parameter.ParameterType.Identity.SpecialType != SpecialType.System_Boolean: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.EscapedParameterInvalidType), + DiagnosticLibrary.Activities.EscapedParameterInvalidType.Descriptor, location ) ); @@ -286,7 +280,7 @@ CancellationToken token case ActivityParameterDestination.Escape when method.MethodType != ActivityMethodType.Event: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.EscapedParameterIsOnlyValidOnEvent), + DiagnosticLibrary.Activities.EscapedParameterIsOnlyValidOnEvent.Descriptor, location, parameterName ) @@ -296,7 +290,7 @@ CancellationToken token when parameter.ParameterType.Identity.SpecialType != SpecialType.System_String: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.StatusDescriptionMustBeString), + DiagnosticLibrary.Activities.StatusDescriptionMustBeString.Descriptor, location ) ); @@ -304,7 +298,7 @@ CancellationToken token case ActivityParameterDestination.StatusDescription when method.MethodType != ActivityMethodType.Event: diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Activities.StatusDescriptionParameterInvalidType), + DiagnosticLibrary.Activities.StatusDescriptionParameterInvalidType.Descriptor, location, parameterName ) diff --git a/src/src/SourceGenerator/Records/TelemetryRules.Logging.cs b/src/src/SourceGenerator/Records/TelemetryRules.Logging.cs index c52e2d23..237646ce 100644 --- a/src/src/SourceGenerator/Records/TelemetryRules.Logging.cs +++ b/src/src/SourceGenerator/Records/TelemetryRules.Logging.cs @@ -19,7 +19,7 @@ CancellationToken token { var diagnostics = ImmutableArray.CreateBuilder(); - if (!Utilities.ContainsAttribute(interfaceSymbol, TemplateLibrary.Logging.LoggerAttribute, token)) + if (!Utilities.ContainsAttribute(interfaceSymbol, TypeLibrary.Logging.LoggerAttribute, token)) return diagnostics.ToImmutable(); var generationType = SharedHelpers.GetGenerationTypes(interfaceSymbol, token); @@ -32,9 +32,7 @@ CancellationToken token var methods = interfaceSymbol .GetMembers() .OfType() - .Where(m => - !Utilities.ContainsAttribute(m, TemplateLibrary.Shared.ExcludeAttribute, token) && m.Arity == 0 - ); + .Where(m => !TypeHelpers.HasAttribute(m, TypeLibrary.TelemetryShared.ExcludeAttribute) && m.Arity == 0); foreach (var method in methods) { @@ -69,7 +67,7 @@ CancellationToken token if (hasMultipleExceptions) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Logging.MultipleExceptionsDefined), + DiagnosticLibrary.Logging.MultipleExceptionsDefined.Descriptor, method, method.Name ) @@ -79,7 +77,7 @@ CancellationToken token if (IsInvalidLogReturnType(method, token)) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Logging.LogMustReturnVoidOrAsync), + DiagnosticLibrary.Logging.LogMustReturnVoidOrAsync.Descriptor, method.ReturnType.Locations ) ); @@ -110,7 +108,7 @@ CancellationToken token if (useV1Generation && nonExceptionCount > PropertyLibrary.Logging.MaxNonExceptionParameters) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Logging.MaximumLogEntryParametersExceeded), + DiagnosticLibrary.Logging.MaximumLogEntryParametersExceeded.Descriptor, method, method.Name ) @@ -118,14 +116,12 @@ CancellationToken token // TSG2002: inferred error level (exception present, no explicit level). if (useV1Generation && !isScoped && exceptionParameters.Length == 1 && logAttribute?.LevelOrNull == null) - diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Logging.InferringErrorLogLevel), method) - ); + diagnostics.Add(DiagnosticInfo.Create(DiagnosticLibrary.Logging.InferringErrorLogLevel.Descriptor, method)); // TSG2007: scoped method must not have an explicit level. if (isScoped && logAttribute?.LevelOrNull != null) diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Logging.ScopedMethodShouldNotHaveLevel), method) + DiagnosticInfo.Create(DiagnosticLibrary.Logging.ScopedMethodShouldNotHaveLevel.Descriptor, method) ); // Per-parameter rules. @@ -154,7 +150,7 @@ CancellationToken token { diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Logging.ExpandEnumerableAndLogPropertiesNotSupported), + DiagnosticLibrary.Logging.ExpandEnumerableAndLogPropertiesNotSupported.Descriptor, parameter ) ); @@ -168,7 +164,7 @@ CancellationToken token ) { diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Logging.UnboundedIEnumerableMaxCount), parameter) + DiagnosticInfo.Create(DiagnosticLibrary.Logging.UnboundedIEnumerableMaxCount.Descriptor, parameter) ); } } @@ -190,7 +186,7 @@ ImmutableArray.Builder diagnostics if (isOrdinalBased && isNamedBased) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Logging.MixedOrdinalAndNamedProperties), + DiagnosticLibrary.Logging.MixedOrdinalAndNamedProperties.Descriptor, method, method.Name ) @@ -203,7 +199,7 @@ ImmutableArray.Builder diagnostics if (maxOrdinal > method.Parameters.Length) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Logging.OrdinalsExceedParameters), + DiagnosticLibrary.Logging.OrdinalsExceedParameters.Descriptor, method, method.Name ) diff --git a/src/src/SourceGenerator/Records/TelemetryRules.Metrics.cs b/src/src/SourceGenerator/Records/TelemetryRules.Metrics.cs index e5e7e70f..066a313c 100644 --- a/src/src/SourceGenerator/Records/TelemetryRules.Metrics.cs +++ b/src/src/SourceGenerator/Records/TelemetryRules.Metrics.cs @@ -45,7 +45,7 @@ ImmutableArray.Builder diagnostics if (instrument.InstrumentAttribute is null) { diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Metrics.NoInstrumentDefined), methodSymbol) + DiagnosticInfo.Create(DiagnosticLibrary.Metrics.NoInstrumentDefined.Descriptor, methodSymbol) ); return; } @@ -60,26 +60,26 @@ ImmutableArray.Builder diagnostics // TSG4001: metrics-owned public method must return void or bool. if (metricsOwnsPublicMethod && !isVoid && !instrument.ReturnsBool) diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Metrics.DoesNotReturnVoid), methodSymbol) + DiagnosticInfo.Create(DiagnosticLibrary.Metrics.DoesNotReturnVoid.Descriptor, methodSymbol) ); // TSG4007: observable instruments cannot return bool. if (instrument.IsObservable && instrument.ReturnsBool) diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Metrics.ObservableCannotReturnBool), methodSymbol) + DiagnosticInfo.Create(DiagnosticLibrary.Metrics.ObservableCannotReturnBool.Descriptor, methodSymbol) ); // TSG4008: auto-counter instruments must return void. if (instrument.InstrumentAttribute.IsAutoIncrement && instrument.ReturnsBool) diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Metrics.AutoCounterMustReturnVoid), methodSymbol) + DiagnosticInfo.Create(DiagnosticLibrary.Metrics.AutoCounterMustReturnVoid.Descriptor, methodSymbol) ); // TSG4002: auto-increment counter cannot also have a measurement parameter. if (instrument.InstrumentAttribute.IsAutoIncrement && instrument.MeasurementParameter != null) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Metrics.AutoIncrementCountAndMeasurementParam), + DiagnosticLibrary.Metrics.AutoIncrementCountAndMeasurementParam.Descriptor, methodSymbol ) ); @@ -90,7 +90,7 @@ ImmutableArray.Builder diagnostics { diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Metrics.MoreThanOneMeasurementValueDefined), + DiagnosticLibrary.Metrics.MoreThanOneMeasurementValueDefined.Descriptor, GetParameterLocation(methodSymbol, measurementParameters[1].ParameterName) ) ); @@ -99,13 +99,13 @@ ImmutableArray.Builder diagnostics // TSG4004: no measurement value defined for a non-auto-increment instrument. if (!instrument.InstrumentAttribute.IsAutoIncrement && instrument.MeasurementParameter is null) diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Metrics.NoMeasurementValueDefined), methodSymbol) + DiagnosticInfo.Create(DiagnosticLibrary.Metrics.NoMeasurementValueDefined.Descriptor, methodSymbol) ); // TSG4005: observable instruments require a Func parameter. if (instrument.IsObservable && !instrument.Parameters.Any(static p => p.IsFunc)) diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.Metrics.ObservableRequiredFunc), methodSymbol) + DiagnosticInfo.Create(DiagnosticLibrary.Metrics.ObservableRequiredFunc.Descriptor, methodSymbol) ); // TSG4006: the measurement parameter has an invalid measurement type. @@ -113,7 +113,7 @@ ImmutableArray.Builder diagnostics { diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Metrics.InvalidMeasurementType), + DiagnosticLibrary.Metrics.InvalidMeasurementType.Descriptor, GetParameterLocation(methodSymbol, measurement.ParameterName) ) ); @@ -127,7 +127,7 @@ ImmutableArray.Builder diagnostics ) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Metrics.InstrumentNameMatchesType), + DiagnosticLibrary.Metrics.InstrumentNameMatchesType.Descriptor, methodSymbol, instrument.MetricName ) diff --git a/src/src/SourceGenerator/Records/TelemetryRules.cs b/src/src/SourceGenerator/Records/TelemetryRules.cs index 8c6fc9d4..b6945c3a 100644 --- a/src/src/SourceGenerator/Records/TelemetryRules.cs +++ b/src/src/SourceGenerator/Records/TelemetryRules.cs @@ -14,69 +14,59 @@ namespace Purview.Telemetry.SourceGenerator; /// static partial class TelemetryRules { - public static DiagnosticDescriptor ToDescriptor(TelemetryDiagnosticDescriptor descriptor) => - new( - id: descriptor.Id, - title: descriptor.Title, - messageFormat: descriptor.Description, - category: descriptor.Category, - defaultSeverity: descriptor.Severity, - isEnabledByDefault: descriptor.EnabledByDefault - ); - public static ImmutableArray GetAllSupportedDescriptors() => [ - ToDescriptor(DiagnosticLibrary.General.FatalExecutionDuringExecution), - ToDescriptor(DiagnosticLibrary.General.InferenceNotSupportedWithMultiTargeting), - ToDescriptor(DiagnosticLibrary.General.MultiGenerationTargetsNotSupported), - ToDescriptor(DiagnosticLibrary.General.DuplicateMethodNamesAreNotSupported), - ToDescriptor(DiagnosticLibrary.General.GenericInterfacesNotSupported), - ToDescriptor(DiagnosticLibrary.General.GenericMethodsNotSupported), - ToDescriptor(DiagnosticLibrary.General.ExcludeTargetsTargetNotPresent), - ToDescriptor(DiagnosticLibrary.General.ExcludeTargetsResultsInEmptyParameterSet), - ToDescriptor(DiagnosticLibrary.General.ActivityParameterWithoutActivityTarget), - ToDescriptor(DiagnosticLibrary.General.MethodTargetNotRegisteredOnInterface), - ToDescriptor(DiagnosticLibrary.General.UnsupportedTargetFramework), - ToDescriptor(DiagnosticLibrary.Logging.MultipleExceptionsDefined), - ToDescriptor(DiagnosticLibrary.Logging.MaximumLogEntryParametersExceeded), - ToDescriptor(DiagnosticLibrary.Logging.InferringErrorLogLevel), - ToDescriptor(DiagnosticLibrary.Logging.MSLoggingNotReferenced), - ToDescriptor(DiagnosticLibrary.Logging.MixedOrdinalAndNamedProperties), - ToDescriptor(DiagnosticLibrary.Logging.OrdinalsExceedParameters), - ToDescriptor(DiagnosticLibrary.Logging.ExpandEnumerableAndLogPropertiesNotSupported), - ToDescriptor(DiagnosticLibrary.Logging.ScopedMethodShouldNotHaveLevel), - ToDescriptor(DiagnosticLibrary.Logging.UnboundedIEnumerableMaxCount), - ToDescriptor(DiagnosticLibrary.Logging.LogMustReturnVoidOrAsync), - ToDescriptor(DiagnosticLibrary.Activities.BaggageParameterShouldBeString), - ToDescriptor(DiagnosticLibrary.Activities.NoActivitySourceSpecified), - ToDescriptor(DiagnosticLibrary.Activities.InvalidReturnType), - ToDescriptor(DiagnosticLibrary.Activities.DuplicateParameterTypes), - ToDescriptor(DiagnosticLibrary.Activities.ActivityParameterNotAllowed), - ToDescriptor(DiagnosticLibrary.Activities.TimestampParameterNotAllowed), - ToDescriptor(DiagnosticLibrary.Activities.StartTimeParameterNotAllowed), - ToDescriptor(DiagnosticLibrary.Activities.ParentContextOrIdParameterNotAllowed), - ToDescriptor(DiagnosticLibrary.Activities.LinksParameterNotAllowed), - ToDescriptor(DiagnosticLibrary.Activities.TagsParameterNotAllowed), - ToDescriptor(DiagnosticLibrary.Activities.EscapedParameterInvalidType), - ToDescriptor(DiagnosticLibrary.Activities.EscapedParameterIsOnlyValidOnEvent), - ToDescriptor(DiagnosticLibrary.Activities.NoActivityMethodsDefined), - ToDescriptor(DiagnosticLibrary.Activities.DoesNotReturnActivity), - ToDescriptor(DiagnosticLibrary.Activities.DoesNotAcceptActivityParameter), - ToDescriptor(DiagnosticLibrary.Activities.ActivityShouldBeTheFirstParameter), - ToDescriptor(DiagnosticLibrary.Activities.StatusDescriptionMustBeString), - ToDescriptor(DiagnosticLibrary.Activities.StatusDescriptionParameterInvalidType), - ToDescriptor(DiagnosticLibrary.Activities.ExceptionEventNotStandardName), - ToDescriptor(DiagnosticLibrary.Activities.ActivityReturnTypeShouldBeNullable), - ToDescriptor(DiagnosticLibrary.Metrics.NoInstrumentDefined), - ToDescriptor(DiagnosticLibrary.Metrics.DoesNotReturnVoid), - ToDescriptor(DiagnosticLibrary.Metrics.AutoIncrementCountAndMeasurementParam), - ToDescriptor(DiagnosticLibrary.Metrics.MoreThanOneMeasurementValueDefined), - ToDescriptor(DiagnosticLibrary.Metrics.NoMeasurementValueDefined), - ToDescriptor(DiagnosticLibrary.Metrics.ObservableRequiredFunc), - ToDescriptor(DiagnosticLibrary.Metrics.InvalidMeasurementType), - ToDescriptor(DiagnosticLibrary.Metrics.ObservableCannotReturnBool), - ToDescriptor(DiagnosticLibrary.Metrics.AutoCounterMustReturnVoid), - ToDescriptor(DiagnosticLibrary.Metrics.InstrumentNameMatchesType), + DiagnosticLibrary.General.FatalExecutionDuringExecution.Descriptor, + DiagnosticLibrary.General.InferenceNotSupportedWithMultiTargeting.Descriptor, + DiagnosticLibrary.General.MultiGenerationTargetsNotSupported.Descriptor, + DiagnosticLibrary.General.DuplicateMethodNamesAreNotSupported.Descriptor, + DiagnosticLibrary.General.GenericInterfacesNotSupported.Descriptor, + DiagnosticLibrary.General.GenericMethodsNotSupported.Descriptor, + DiagnosticLibrary.General.ExcludeTargetsTargetNotPresent.Descriptor, + DiagnosticLibrary.General.ExcludeTargetsResultsInEmptyParameterSet.Descriptor, + DiagnosticLibrary.General.ActivityParameterWithoutActivityTarget.Descriptor, + DiagnosticLibrary.General.MethodTargetNotRegisteredOnInterface.Descriptor, + DiagnosticLibrary.General.UnsupportedTargetFramework.Descriptor, + DiagnosticLibrary.Logging.MultipleExceptionsDefined.Descriptor, + DiagnosticLibrary.Logging.MaximumLogEntryParametersExceeded.Descriptor, + DiagnosticLibrary.Logging.InferringErrorLogLevel.Descriptor, + DiagnosticLibrary.Logging.MSLoggingNotReferenced.Descriptor, + DiagnosticLibrary.Logging.MixedOrdinalAndNamedProperties.Descriptor, + DiagnosticLibrary.Logging.OrdinalsExceedParameters.Descriptor, + DiagnosticLibrary.Logging.ExpandEnumerableAndLogPropertiesNotSupported.Descriptor, + DiagnosticLibrary.Logging.ScopedMethodShouldNotHaveLevel.Descriptor, + DiagnosticLibrary.Logging.UnboundedIEnumerableMaxCount.Descriptor, + DiagnosticLibrary.Logging.LogMustReturnVoidOrAsync.Descriptor, + DiagnosticLibrary.Activities.BaggageParameterShouldBeString.Descriptor, + DiagnosticLibrary.Activities.NoActivitySourceSpecified.Descriptor, + DiagnosticLibrary.Activities.InvalidReturnType.Descriptor, + DiagnosticLibrary.Activities.DuplicateParameterTypes.Descriptor, + DiagnosticLibrary.Activities.ActivityParameterNotAllowed.Descriptor, + DiagnosticLibrary.Activities.TimestampParameterNotAllowed.Descriptor, + DiagnosticLibrary.Activities.StartTimeParameterNotAllowed.Descriptor, + DiagnosticLibrary.Activities.ParentContextOrIdParameterNotAllowed.Descriptor, + DiagnosticLibrary.Activities.LinksParameterNotAllowed.Descriptor, + DiagnosticLibrary.Activities.TagsParameterNotAllowed.Descriptor, + DiagnosticLibrary.Activities.EscapedParameterInvalidType.Descriptor, + DiagnosticLibrary.Activities.EscapedParameterIsOnlyValidOnEvent.Descriptor, + DiagnosticLibrary.Activities.NoActivityMethodsDefined.Descriptor, + DiagnosticLibrary.Activities.DoesNotReturnActivity.Descriptor, + DiagnosticLibrary.Activities.DoesNotAcceptActivityParameter.Descriptor, + DiagnosticLibrary.Activities.ActivityShouldBeTheFirstParameter.Descriptor, + DiagnosticLibrary.Activities.StatusDescriptionMustBeString.Descriptor, + DiagnosticLibrary.Activities.StatusDescriptionParameterInvalidType.Descriptor, + DiagnosticLibrary.Activities.ExceptionEventNotStandardName.Descriptor, + DiagnosticLibrary.Activities.ActivityReturnTypeShouldBeNullable.Descriptor, + DiagnosticLibrary.Metrics.NoInstrumentDefined.Descriptor, + DiagnosticLibrary.Metrics.DoesNotReturnVoid.Descriptor, + DiagnosticLibrary.Metrics.AutoIncrementCountAndMeasurementParam.Descriptor, + DiagnosticLibrary.Metrics.MoreThanOneMeasurementValueDefined.Descriptor, + DiagnosticLibrary.Metrics.NoMeasurementValueDefined.Descriptor, + DiagnosticLibrary.Metrics.ObservableRequiredFunc.Descriptor, + DiagnosticLibrary.Metrics.InvalidMeasurementType.Descriptor, + DiagnosticLibrary.Metrics.ObservableCannotReturnBool.Descriptor, + DiagnosticLibrary.Metrics.AutoCounterMustReturnVoid.Descriptor, + DiagnosticLibrary.Metrics.InstrumentNameMatchesType.Descriptor, ]; static IMethodSymbol? FindMethod(INamedTypeSymbol interfaceSymbol, string methodName) => @@ -128,10 +118,7 @@ CancellationToken token // TSG1011: unsupported target framework. if (IsUnsupportedTargetFramework(compilation)) diagnostics.Add( - DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.UnsupportedTargetFramework), - interfaceSymbol - ) + DiagnosticInfo.Create(DiagnosticLibrary.General.UnsupportedTargetFramework.Descriptor, interfaceSymbol) ); // TSG1004: generic interface - nothing further is meaningful. @@ -139,7 +126,7 @@ CancellationToken token { diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.GenericInterfacesNotSupported), + DiagnosticLibrary.General.GenericInterfacesNotSupported.Descriptor, interfaceSymbol ) ); @@ -148,11 +135,11 @@ CancellationToken token var hasActivitySource = Utilities.ContainsAttribute( interfaceSymbol, - TemplateLibrary.Activities.ActivitySourceAttribute, + TypeLibrary.Activities.ActivitySourceAttribute, token ); - var hasLogger = Utilities.ContainsAttribute(interfaceSymbol, TemplateLibrary.Logging.LoggerAttribute, token); - var hasMeter = Utilities.ContainsAttribute(interfaceSymbol, TemplateLibrary.Metrics.MeterAttribute, token); + var hasLogger = Utilities.ContainsAttribute(interfaceSymbol, TypeLibrary.Logging.LoggerAttribute, token); + var hasMeter = Utilities.ContainsAttribute(interfaceSymbol, TypeLibrary.Metrics.MeterAttribute, token); if (!hasActivitySource && !hasLogger && !hasMeter) return diagnostics.ToImmutable(); @@ -165,10 +152,7 @@ CancellationToken token ); if (iLoggerSymbol is null) diagnostics.Add( - DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.Logging.MSLoggingNotReferenced), - interfaceSymbol - ) + DiagnosticInfo.Create(DiagnosticLibrary.Logging.MSLoggingNotReferenced.Descriptor, interfaceSymbol) ); } @@ -198,7 +182,7 @@ CancellationToken token var locations = methods.SelectMany(static m => m.Locations).ToImmutableArray(); diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.DuplicateMethodNamesAreNotSupported), + DiagnosticLibrary.General.DuplicateMethodNamesAreNotSupported.Descriptor, locations, kvp.Key ) @@ -228,14 +212,14 @@ static void ApplyPerMethodRules( CancellationToken token ) { - if (Utilities.ContainsAttribute(method, TemplateLibrary.Shared.ExcludeAttribute, token)) + if (TypeHelpers.HasAttribute(method, TypeLibrary.TelemetryShared.ExcludeAttribute)) return; // TSG1005: generic method. if (method.Arity > 0) { diagnostics.Add( - DiagnosticInfo.Create(ToDescriptor(DiagnosticLibrary.General.GenericMethodsNotSupported), method) + DiagnosticInfo.Create(DiagnosticLibrary.General.GenericMethodsNotSupported.Descriptor, method) ); return; } @@ -245,25 +229,19 @@ CancellationToken token if (targetState.RaiseInferenceNotSupportedWithMultiTargeting) diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.InferenceNotSupportedWithMultiTargeting), + DiagnosticLibrary.General.InferenceNotSupportedWithMultiTargeting.Descriptor, method ) ); if (targetState.RaiseMultiGenerationTargetsNotSupported) diagnostics.Add( - DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.MultiGenerationTargetsNotSupported), - method - ) + DiagnosticInfo.Create(DiagnosticLibrary.General.MultiGenerationTargetsNotSupported.Descriptor, method) ); if (targetState.RaiseMissingInterfaceSource) diagnostics.Add( - DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.MethodTargetNotRegisteredOnInterface), - method - ) + DiagnosticInfo.Create(DiagnosticLibrary.General.MethodTargetNotRegisteredOnInterface.Descriptor, method) ); // TSG1008: an Activity parameter on a method with no Activity target will be ignored. @@ -282,7 +260,7 @@ targetState.ActivityParameterWithoutTarget is { } activityParameterName diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.ActivityParameterWithoutActivityTarget), + DiagnosticLibrary.General.ActivityParameterWithoutActivityTarget.Descriptor, activityParameterLocation, activityParameterName ) @@ -333,7 +311,7 @@ CancellationToken token var location = parameter.Locations.FirstOrDefault(static l => l.IsInSource) ?? Location.None; diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.ExcludeTargetsTargetNotPresent), + DiagnosticLibrary.General.ExcludeTargetsTargetNotPresent.Descriptor, location, GetGenerationTypeName(target) ) @@ -348,7 +326,7 @@ CancellationToken token { diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.ExcludeTargetsResultsInEmptyParameterSet), + DiagnosticLibrary.General.ExcludeTargetsResultsInEmptyParameterSet.Descriptor, method, GetGenerationTypeName(target), method.Name @@ -384,17 +362,14 @@ CancellationToken token if (IsUnsupportedTargetFramework(compilation)) diagnostics.Add( - DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.UnsupportedTargetFramework), - interfaceSymbol - ) + DiagnosticInfo.Create(DiagnosticLibrary.General.UnsupportedTargetFramework.Descriptor, interfaceSymbol) ); if (interfaceSymbol.Arity > 0) { diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.GenericInterfacesNotSupported), + DiagnosticLibrary.General.GenericInterfacesNotSupported.Descriptor, interfaceSymbol ) ); @@ -426,7 +401,7 @@ CancellationToken token var locations = kvp.Value.SelectMany(static m => m.Locations).ToImmutableArray(); diagnostics.Add( DiagnosticInfo.Create( - ToDescriptor(DiagnosticLibrary.General.DuplicateMethodNamesAreNotSupported), + DiagnosticLibrary.General.DuplicateMethodNamesAreNotSupported.Descriptor, locations, kvp.Key ) diff --git a/src/src/SourceGenerator/Templates/TemplateInfo.cs b/src/src/SourceGenerator/Templates/TemplateInfo.cs deleted file mode 100644 index b6eb51cb..00000000 --- a/src/src/SourceGenerator/Templates/TemplateInfo.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Purview.Telemetry.SourceGenerator.Templates; - -/// -/// Identity of a marker-attribute template. The template source is not held in memory; -/// it is emitted with a during RegisterPostInitializationOutput -/// (see MarkerAttributeTemplateEmitter). -/// -sealed record TemplateInfo(TypeIdentity TypeInfo) : IEquatable -{ - public string Name => TypeInfo.Name; - - public string GetGeneratedFilename() => $"{Name}.g.cs"; - - public bool Equals(TypeIdentity other) => other.Equals(TypeInfo); - - public static TemplateInfo Create(TypeIdentity type) => new(type); - - public static implicit operator string(TemplateInfo templateInfo) => templateInfo.TypeInfo.RenderFullName; -} diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicEventGen.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicEventGen.cs index 9a5272ae..ca1fdc7a 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicEventGen.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicEventGen.cs @@ -189,12 +189,7 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3021"); } [Test] @@ -225,12 +220,7 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3021"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicGen.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicGen.cs index d9cdf97f..0aee2ae3 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicGen.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicGen.cs @@ -61,12 +61,7 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3012"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3012"); } [Test] @@ -128,12 +123,7 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3000"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3000"); } [Test] @@ -164,12 +154,7 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3022"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3022"); } [Test] @@ -203,12 +188,7 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3022"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3022"); } [Test] @@ -265,11 +245,6 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3022"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3022"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.CSharp73.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.CSharp73.cs index 4383b839..0f2d5e9c 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.CSharp73.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.CSharp73.cs @@ -29,7 +29,7 @@ public interface ITestLogger { // Act var generationResult = await GenerateAsync(source, cancellationToken: cancellationToken); - // Assert: validationCompilation=true (default) verifies generated code compiles under C# 7.3. + // Assert: GenerateAsync's EnsureValid (default) verifies generated code compiles under C# 7.3. await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExpandEnumerable.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExpandEnumerable.cs index af4dd7c6..3002dd3e 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExpandEnumerable.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExpandEnumerable.cs @@ -61,12 +61,7 @@ public interface ITestLogger ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG2008"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2008"); } public static IEnumerable ExpandableArrays diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.PerMethodV1.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.PerMethodV1.cs index 2ee16b64..551a00ce 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.PerMethodV1.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.PerMethodV1.cs @@ -81,11 +81,6 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG2001"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2001"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.BasicGen.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.BasicGen.cs index 2ef8647f..a69b64c2 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.BasicGen.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.BasicGen.cs @@ -133,12 +133,7 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - cancellationToken: cancellationToken - ); + await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); } [Test] @@ -166,12 +161,7 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - cancellationToken: cancellationToken - ); + await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.CSharp73.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.CSharp73.cs index 10112da7..b5f6cbf2 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.CSharp73.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.CSharp73.cs @@ -27,7 +27,7 @@ public interface ITestLogger { // Act var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); - // Assert: validationCompilation=true (default) verifies generated code compiles under C# 7.3. + // Assert: GenerateAsync's EnsureValid (default) verifies generated code compiles under C# 7.3. await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.CSharp73.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.CSharp73.cs index bff27f33..61abf60c 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.CSharp73.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.CSharp73.cs @@ -40,7 +40,7 @@ public interface ITestMetrics { // Act var generationResult = await GenerateAsync(basicMetrics, cancellationToken: cancellationToken); - // Assert: validationCompilation=true (default) verifies generated code compiles under C# 7.3. + // Assert: GenerateAsync's EnsureValid (default) verifies generated code compiles under C# 7.3. await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Counters.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Counters.cs index 2fdc1e51..d8893d15 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Counters.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Counters.cs @@ -59,13 +59,12 @@ public interface ITestMetrics ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG4002"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG4002"); + //await Assert + // .That(generationResult) + // .HasDiagnostic(DiagnosticLibrary.Metrics.AutoIncrementCountAndMeasurementParam); + + await Assert.That(generationResult).HasDiagnostic("TSG4002"); } [Test] @@ -121,13 +120,7 @@ public interface ITestMetrics ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG4002"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG4002"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs index 31230458..4537249c 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs @@ -85,12 +85,7 @@ public interface ITestMetrics<{{genericTypeDef}}> { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1004"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1004"); } [Test] @@ -123,11 +118,6 @@ public interface ITestMetrics<{{genericTypeDef}}> { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1004"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1004"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.Docs_README.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.Docs_README.cs index 4e846158..f3d9bd4b 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.Docs_README.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.Docs_README.cs @@ -116,12 +116,7 @@ interface IActivityTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3021"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.DuplicateMethodNames.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.DuplicateMethodNames.cs index b3f3d5c4..6a9a5c77 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.DuplicateMethodNames.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.DuplicateMethodNames.cs @@ -33,12 +33,7 @@ public interface ITestTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1003"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1003"); } [Test] @@ -73,12 +68,7 @@ public interface ITestTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1003"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1003"); } [Test] @@ -107,12 +97,7 @@ public interface ITestTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1003"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1003"); } [Test] @@ -141,12 +126,7 @@ public interface ITestTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1003"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1003"); } [Test] @@ -196,11 +176,6 @@ public interface ITestTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1003"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1003"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.InvalidReturnTypes.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.InvalidReturnTypes.cs index 5045187f..472cb8fd 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.InvalidReturnTypes.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.InvalidReturnTypes.cs @@ -32,13 +32,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); } [Test] @@ -67,13 +61,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG4001"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG4001"); } [Test] @@ -102,13 +90,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG3002"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3002"); } [Test] @@ -137,13 +119,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); } [Test] @@ -172,13 +148,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); } [Test] @@ -207,13 +177,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); } [Test] @@ -243,13 +207,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG4001"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG4001"); } [Test] @@ -281,13 +239,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG3002"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3002"); } [Test] @@ -345,13 +297,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG4007"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG4007"); } [Test] @@ -380,13 +326,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG4008"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG4008"); } [Test] @@ -421,13 +361,7 @@ string eventName ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG3002"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3002"); } [Test] @@ -463,13 +397,7 @@ string value ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG3002"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3002"); } [Test] @@ -497,13 +425,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); } [Test] @@ -531,13 +453,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); } [Test] @@ -568,13 +484,7 @@ public interface IInvalidTelemetry ); // Assert - Task is not a valid return type for logging - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); } [Test] @@ -605,13 +515,7 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); } [Test] @@ -642,12 +546,6 @@ public interface IInvalidTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG4001"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG4001"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.MultiGeneration.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.MultiGeneration.cs index f0ec70d7..242ccb34 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.MultiGeneration.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.MultiGeneration.cs @@ -287,12 +287,7 @@ public void MethodWithoutAttribute(string message) ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1001"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1001"); } [Test] @@ -588,12 +583,7 @@ public interface IMultiTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1003"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1003"); } [Test] @@ -668,13 +658,7 @@ public interface IMultiTelemetry ); // Assert - Task and ValueTask are not valid return types for logging - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - expectedDiagnosticCodes: ["TSG2021"], // Async return types are invalid - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG2021"); // Async return types are invalid } [Test] @@ -745,12 +729,7 @@ public interface IMultiTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3012"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3012"); } [Test] @@ -784,12 +763,7 @@ public interface ITelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1010"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1010"); } [Test] @@ -823,12 +797,7 @@ public interface ITelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1010"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1010"); } [Test] @@ -862,12 +831,7 @@ public interface ITelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1010"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1010"); } [Test] @@ -899,11 +863,6 @@ public interface ITelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG1010"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG1010"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryBasic.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryBasic.cs index 3790c5d9..abfa4f0a 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryBasic.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryBasic.cs @@ -177,12 +177,7 @@ public interface ITestTelemetry var generationResult = await GenerateAsync(basicTelemetry, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3021"); } [Test] @@ -212,12 +207,7 @@ public interface ITestTelemetry var generationResult = await GenerateAsync(basicTelemetry, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3021"); } [Test] @@ -247,12 +237,7 @@ public interface ITestTelemetry var generationResult = await GenerateAsync(basicTelemetry, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - expectedDiagnosticCodes: ["TSG3021"], - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasDiagnostic("TSG3021"); } [Test] @@ -365,11 +350,6 @@ public interface ITestTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: true, - validationCompilation: false, - cancellationToken: cancellationToken - ); + await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryNamesNamespace.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryNamesNamespace.cs new file mode 100644 index 00000000..bf4315b9 --- /dev/null +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryNamesNamespace.cs @@ -0,0 +1,100 @@ +using Purview.Telemetry.SourceGenerator.Infra; + +namespace Purview.Telemetry.SourceGenerator; + +partial class TelemetrySourceGeneratorTests +{ + [Test] + public async Task Generate_GivenTelemetryNamesNamespace_GeneratesAllTypesInThatNamespace( + CancellationToken cancellationToken + ) + { + // Arrange + const string source = """ + using Purview.Telemetry; + + [assembly: TelemetryGeneration(GenerateDependencyExtension = true, TelemetryNamesNamespace = "Custom.Telemetry")] + + namespace Testing; + + [ActivitySource("testing-activity-source")] + public interface ITestActivities { + [Activity] + System.Diagnostics.Activity? Activity(string? parentId); + } + + [Meter("testing-meter")] + public interface ITestMetrics { + [Counter] + void Counter([InstrumentMeasurement]int value); + } + + [Logger] + public interface ITestLogger { + void Log(int value); + } + """; + + // Act + var generationResult = await GenerateAsync( + source, + GenerateDependencyInjection(), + cancellationToken: cancellationToken + ); + + // Assert + await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + + await Assert + .That(generationResult.GetSource("TestActivitiesCore.Activity.g.cs")) + .ContainsGeneratedCode("namespace Custom.Telemetry"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("namespace Custom.Telemetry"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode("namespace Custom.Telemetry"); + + await Assert + .That(generationResult.GetSource("TestActivitiesCoreDIExtension.DependencyInjection.g.cs")) + .ContainsGeneratedCode("namespace Custom.Telemetry"); + + await Assert + .That(generationResult.GetSource("TelemetryNames.g.cs")) + .ContainsGeneratedCode("namespace Custom.Telemetry"); + } + + [Test] + public async Task Generate_GivenNoTelemetryNamesNamespace_UsesInterfaceNamespaces( + CancellationToken cancellationToken + ) + { + // Arrange + const string source = """ + using Purview.Telemetry; + + [assembly: TelemetryGeneration(GenerateDependencyExtension = true)] + + namespace Testing; + + [Logger] + public interface ITestLogger { + void Log(int value); + } + """; + + // Act + var generationResult = await GenerateAsync( + source, + GenerateDependencyInjection(), + cancellationToken: cancellationToken + ); + + // Assert + await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode("namespace Testing"); + } +} diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetrySourceGeneratorTestOptions.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetrySourceGeneratorTestOptions.cs index 17d0c2b4..0adb5d03 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetrySourceGeneratorTestOptions.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetrySourceGeneratorTestOptions.cs @@ -2,7 +2,9 @@ using System.Diagnostics.Metrics; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Analyzers; +using Purview.Telemetry.SourceGenerator.Helpers; namespace Purview.Telemetry.SourceGenerator.Infra; @@ -25,6 +27,15 @@ public TelemetrySourceGeneratorTestOptions() // Most tests do not want a dependency-injection extension generated. AdditionalSources = ["[assembly: Purview.Telemetry.TelemetryGeneration(GenerateDependencyExtension = false)]"]; + + ExcludeGeneratedSourceHintNames = + [ + PurviewTypeLibrary.Microsoft.CodeAnalysis.EmbeddedAttribute, + .. TypeLibrary.TelemetryShared.GetGeneratedTypes().Select(c => c.MetadataFullName), + .. TypeLibrary.Activities.GetGeneratedTypes().Select(c => c.MetadataFullName), + .. TypeLibrary.Logging.GetGeneratedTypes().Select(c => c.MetadataFullName), + .. TypeLibrary.Metrics.GetGeneratedTypes().Select(c => c.MetadataFullName), + ]; } /// diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs index 6f3aed63..34b8ad02 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs @@ -1,19 +1,7 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; - namespace Purview.Telemetry.SourceGenerator.Infra; static class TestHelpers { - public const string DefaultUsingSet = - @" -using System; -using Purview.Telemetry; - -"; - - public static string Wrap(this string value, char c = '"') => c + value + c; - /// Replaces all occurrences using ordinal semantics (net48 lacks the StringComparison overload). public static string ReplaceOrdinal(this string value, string oldValue, string newValue) => #if NET48 @@ -22,21 +10,6 @@ public static string ReplaceOrdinal(this string value, string oldValue, string n value.Replace(oldValue, newValue, StringComparison.Ordinal); #endif - /// Throws if the argument is null (net48 lacks ArgumentNullException.ThrowIfNull). -#if NET8_0_OR_GREATER - [StackTraceHidden] -#endif - [DebuggerHidden] - public static void ThrowIfNull(object? argument, [CallerMemberName] string? paramName = null) - { -#if NET48 - if (argument is null) - throw new ArgumentNullException(paramName); -#else - ArgumentNullException.ThrowIfNull(argument, paramName); -#endif - } - public static List GetCasePermutations(string input) { List result = []; @@ -72,11 +45,11 @@ public static async Task VerifyAsync( DriverRunResult result, bool expectsDiagnostics = false, bool whenValidatingDiagnosticsIgnoreNonErrors = false, - bool validationCompilation = true, - string[]? expectedDiagnosticCodes = null, CancellationToken cancellationToken = default ) { + cancellationToken.ThrowIfCancellationRequested(); + var diag = result.DriverResult.Diagnostics.AddRange(result.AnalyzerResult?.Diagnostics ?? []).ToArray(); if (whenValidatingDiagnosticsIgnoreNonErrors) diag = [.. diag.Where(m => m.Severity == DiagnosticSeverity.Error)]; @@ -84,20 +57,6 @@ public static async Task VerifyAsync( if (expectsDiagnostics) { await Assert.That(diag).IsNotEmpty(); - - if (expectedDiagnosticCodes?.Length > 0) - { - var actualDiagnosticCodes = diag.Select(d => d.Id).Distinct().ToArray(); - var expectedCodes = expectedDiagnosticCodes.ToArray(); - - await Assert - .That(actualDiagnosticCodes) - .IsEquivalentTo(expectedCodes) - .Because( - $"Expected diagnostic codes: [{string.Join(", ", expectedCodes)}], " - + $"but found: [{string.Join(", ", actualDiagnosticCodes)}]" - ); - } } else { @@ -108,26 +67,5 @@ await Assert $"Expected no diagnostics, but found: [{string.Join(", ", diag.Select(d => d.Id).Distinct())}]" ); } - - if (!validationCompilation) - return; - - using MemoryStream ms = new(); - - var emitResult = result.CompilationResult.Compilation.Emit(ms, cancellationToken: cancellationToken); - if (!emitResult.Success) - { - await Assert - .That(emitResult.Diagnostics.Where(m => !m.Id.StartsWith("TSG", StringComparison.Ordinal))) - .IsEmpty() - .Because( - string.Join( - Environment.NewLine, - emitResult.Diagnostics.Select(d => - $"{d}{Environment.NewLine}-----------------------------------------------------" - ) - ) - ); - } } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs b/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs index d162c242..bd2017bc 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs @@ -99,8 +99,8 @@ protected static async Task> GetRefactoringActionsAsyn CancellationToken cancellationToken = default ) { - TestHelpers.ThrowIfNull(codeWithMarker); - TestHelpers.ThrowIfNull(provider); + await Assert.That(codeWithMarker).IsNotNullOrWhiteSpace(); + await Assert.That(provider).IsNotNull(); const string marker = "$$"; @@ -203,8 +203,8 @@ protected static async Task VerifyRefactoringAsync( CancellationToken cancellationToken = default ) { - TestHelpers.ThrowIfNull(codeWithMarker, nameof(codeWithMarker)); - TestHelpers.ThrowIfNull(provider, nameof(provider)); + await Assert.That(codeWithMarker).IsNotNullOrWhiteSpace(); + await Assert.That(provider).IsNotNull(); var after = await ApplyRefactoringAsync(codeWithMarker, provider, equivalenceKey, cancellationToken); From cde9c2a74dd0e1891eecfb05c70397aa1d170ac2 Mon Sep 17 00:00:00 2001 From: Kieron Lanning Date: Thu, 3 Sep 2026 08:35:13 +0100 Subject: [PATCH 3/3] feat: updated the samples to use the latest code --- .agents/agents/test-author-writer.agent.md | 50 ++++ ...odernize-test-to-codequery-tunit.prompt.md | 48 ++++ .../sdk-configuration-reference/SKILL.md | 4 +- .../SKILL.md | 7 + .../source-generator-testing/.gitignore | 8 + .../skills/tunit-test-authoring/.gitignore | 8 + .changeset/README.md | 11 - .changeset/config.json | 14 - .github/workflows/pr.yml | 39 +++ Directory.Packages.props | 28 +- global.json | 3 +- package.json | 12 +- samples/Directory.Build.targets | 3 + samples/SampleApp.Net48/Directory.Build.props | 23 +- samples/SampleApp.Net48/README.md | 17 +- .../SampleApp.Net48.ConsoleApp/Program.cs | 8 +- .../SampleApp.Net48.ConsoleApp.csproj | 20 +- .../Services/IWeatherService.cs | 2 - .../Services/IWeatherServiceTelemetry.cs | 1 - .../Services/WeatherService.cs | 7 +- samples/SampleApp.Net48/SampleApp.Net48.slnx | 1 + samples/SampleApp/Directory.Build.props | 29 ++- samples/SampleApp/README.md | 11 +- .../SampleApp.APIService.UnitTests.csproj | 21 -- .../Services/WeatherServiceTests.Failure.cs | 6 +- .../Services/WeatherServiceTests.Success.cs | 4 +- .../WeatherServiceTests.Validation.cs | 4 +- .../Services/WeatherServiceTests.cs | 6 +- .../Endpoints/WeatherEndpoints.cs | 1 + .../SampleApp/SampleApp.APIService/Program.cs | 1 - .../SampleApp.APIService.csproj | 19 +- .../Services/IWeatherServiceTelemetry.cs | 4 +- .../Services/WeatherService.cs | 4 +- .../SampleApp.AppHost.csproj | 11 +- .../SampleApp.ServiceDefaults.csproj | 45 ++-- .../WebApplicationExtensions.cs | 4 +- .../SampleApp.Shared/SampleApp.Shared.csproj | 6 +- .../Clients/IWeatherAPIClientTelemetry.cs | 4 +- samples/SampleApp/SampleApp.Web/Program.cs | 1 - .../SampleApp.Web/SampleApp.Web.csproj | 11 +- samples/SampleApp/SampleApp.slnx | 17 +- src/Directory.Build.props | 7 +- .../Emitters/GeneratedTypesEmitter.cs | 2 + .../LoggerGenTargetClassEmitter.Methods.cs | 12 +- .../SourceGenerator/SourceGenerator.csproj | 11 +- .../TelemetryDiagnosticAnalyzerTests.cs | 115 +++++++++ ...eGeneratorActivityTests.BasicContextGen.cs | 169 +++++++++++- ...rceGeneratorActivityTests.BasicEventGen.cs | 98 ++++++- ...rySourceGeneratorActivityTests.BasicGen.cs | 95 ++++++- ...rySourceGeneratorActivityTests.CSharp73.cs | 36 ++- ...elemetrySourceGeneratorActivityTests.DI.cs | 115 ++++++++- ...ourceGeneratorActivityTests.ParentTests.cs | 43 +++- .../TelemetrySourceGeneratorActivityTests.cs | 19 +- .../TelemetrySourceGeneratorCachingTests.cs | 241 +++++++++++------- ...ourceGeneratorLoggingGen2Tests.CSharp73.cs | 17 +- ...eneratorLoggingGen2Tests.ExceptionTests.cs | 29 ++- ...eratorLoggingGen2Tests.ExpandEnumerable.cs | 7 +- ...GeneratorLoggingGen2Tests.LogProperties.cs | 105 +++++++- ...ceGeneratorLoggingGen2Tests.PerMethodV1.cs | 23 +- ...urceGeneratorLoggingGen2Tests.ScopedGen.cs | 73 +++++- ...elemetrySourceGeneratorLoggingGen2Tests.cs | 18 +- ...trySourceGeneratorLoggingTests.BasicGen.cs | 94 ++++++- ...trySourceGeneratorLoggingTests.CSharp73.cs | 17 +- ...TelemetrySourceGeneratorLoggingTests.DI.cs | 82 +++++- ...ourceGeneratorLoggingTests.LogAttribute.cs | 40 ++- ...metrySourceGeneratorLoggingTests.Nested.cs | 35 ++- ...ourceGeneratorLoggingTests.NoILoggerRef.cs | 26 +- ...eratorLoggingTests.SpecificLogAttribute.cs | 92 ++++++- .../TelemetrySourceGeneratorLoggingTests.cs | 11 +- ...trySourceGeneratorMetricsTests.CSharp73.cs | 30 ++- ...trySourceGeneratorMetricsTests.Counters.cs | 59 ++++- ...TelemetrySourceGeneratorMetricsTests.DI.cs | 82 +++++- ...emetrySourceGeneratorMetricsTests.Gauge.cs | 17 +- ...rySourceGeneratorMetricsTests.Histogram.cs | 29 ++- ...SourceGeneratorMetricsTests.MeterNaming.cs | 123 ++++++++- ...eratorMetricsTests.NameUnitsDescription.cs | 16 +- ...eneratorMetricsTests.ObservableSpecific.cs | 32 ++- ...ourceGeneratorMetricsTests.TagListCount.cs | 22 +- ...metrySourceGeneratorMetricsTests.UpDown.cs | 44 +++- .../TelemetrySourceGeneratorMetricsTests.cs | 35 ++- ...lemetrySourceGeneratorTests.Docs_README.cs | 110 +++++++- .../TelemetrySourceGeneratorTests.IRL.cs | 26 +- ...SourceGeneratorTests.InvalidReturnTypes.cs | 8 +- ...trySourceGeneratorTests.MultiGeneration.cs | 232 +++++++++++++++-- ...etrySourceGeneratorTests.TelemetryBasic.cs | 83 +++++- ...eGeneratorTests.TelemetryNamesNamespace.cs | 6 - .../TelemetrySourceGeneratorTests.cs | 18 +- .../Infra/TelemetryAnalyzerTestOptions.cs | 35 +++ .../TelemetrySourceGeneratorTestOptions.cs | 3 + .../Infra/TelemetryTestAttributeSource.cs | 71 ++++++ .../Infra/TestHelpers.cs | 28 -- .../Refactorings/CodeRefactoringTestBase.cs | 9 + .../TelemetrySourceGeneratorCachingTests.cs | 184 ------------- 93 files changed, 2767 insertions(+), 690 deletions(-) create mode 100644 .agents/agents/test-author-writer.agent.md create mode 100644 .agents/prompts/modernize-test-to-codequery-tunit.prompt.md create mode 100644 .agents/skills/source-generator-testing/.gitignore create mode 100644 .agents/skills/tunit-test-authoring/.gitignore delete mode 100644 .changeset/README.md delete mode 100644 .changeset/config.json create mode 100644 samples/Directory.Build.targets create mode 100644 src/tests/SourceGenerator.IntegrationTests/Analyzers/TelemetryDiagnosticAnalyzerTests.cs create mode 100644 src/tests/SourceGenerator.IntegrationTests/Infra/TelemetryAnalyzerTestOptions.cs create mode 100644 src/tests/SourceGenerator.IntegrationTests/Infra/TelemetryTestAttributeSource.cs delete mode 100644 src/tests/SourceGenerator.IntegrationTests/TelemetrySourceGeneratorCachingTests.cs diff --git a/.agents/agents/test-author-writer.agent.md b/.agents/agents/test-author-writer.agent.md new file mode 100644 index 00000000..fb5fb87d --- /dev/null +++ b/.agents/agents/test-author-writer.agent.md @@ -0,0 +1,50 @@ +--- +name: Test Author Writer +description: "Specialist for Purview.SourceGeneratorFramework test suites — writing, fixing, and modernising TUnit tests for generators, diagnostic analyzers, code fixes, and refactorings, and for adding stage-by-stage incremental cache tests." +tools: + [ + "search/codebase", + "edit/editFiles", + "search", + "execute/getTerminalOutput", + "execute/runInTerminal", + "read/terminalLastCommand", + "read/terminalSelection", + "execute/createAndRunTask", + "execute/runTask", + "read/getTaskOutput", + "vscodeTasks/createAndRunTask", + "vscodeTasks/getTaskOutput", + "vscodeTasks/runTask", + ] +--- + +You are a specialist for `Purview.SourceGeneratorFramework` test authoring. + +## Primary objective + +Produce correct, maintainable TUnit tests for source generators, diagnostic analyzers, code fix +providers, and refactoring providers, and prove incremental pipelines cache correctly. + +## Background knowledge + +Before writing or changing any test, load and apply the `source-generator-testing` skill (runner layer, +result types, `CodeQuery`, options, cache testing) and the `tunit-test-authoring` skill (base classes, +methods, assertion extensions, modernisation checklist). For source-generator emission work, also load the +`source-generator-codewriter-modernization` skill. + +Key rules: + +- Pick the base class by the Roslyn component type: generator → `TUnitSourceGeneratorTestBase` + + `GenerateAsync`; analyzer → `TUnitDiagnosticAnalyzerTestBase` + `AnalyzeAsync`; code fix → + `TUnitCodeFixTestBase` + `ApplyCodeFixAsync`/`ApplyFixAllAsync`; refactor → + `TUnitRefactoringTestBase` + `RefactorAsync`. +- Prefer `CodeQuery` (`result.Generated()` / `result.FixedCode()` with `Get/Has/TryGet`) over + raw-string assertions. +- Prefer the terminal assertion extensions (`HasGeneratedMethod`, `HasGeneratedClass`, …) that return + syntax nodes. +- Derive a `SourceGeneratorTestOptions` record that seeds namespaces and additional assemblies. +- For incremental pipelines, add a stage-by-stage cache test with `RunIncrementalAsync` / + `GenerateIncrementalAsync`, asserting `New` on first run and `Cached`/`Unchanged` on an identical rerun, + and `Modified` only on the stages whose inputs changed. +- Keep generated-output assertions deterministic (no timestamps); enable CodeWriter scope validation. \ No newline at end of file diff --git a/.agents/prompts/modernize-test-to-codequery-tunit.prompt.md b/.agents/prompts/modernize-test-to-codequery-tunit.prompt.md new file mode 100644 index 00000000..cf463ceb --- /dev/null +++ b/.agents/prompts/modernize-test-to-codequery-tunit.prompt.md @@ -0,0 +1,48 @@ +--- +agent: ask +description: "Modernise a Roslyn test suite to use CodeQuery + TUnit assertion extensions, and add a stage-by-stage incremental cache test." +--- + +You are modernising tests in this repository. Apply the guidance from the `source-generator-testing` and +`tunit-test-authoring` skills for picking the right base class, querying generated code with `CodeQuery`, +and asserting incremental caching. + +## Inputs + +- Target test file(s): `${input:targetFiles:Path(s) to test file(s)}` +- Roslyn component under test: `${input:componentType:generator|analyzer|codefix|refactor}` (inferred if blank) +- Generator/analyzer/code-fix/refactor type name: `${input:componentName:Component type name}` + +## Task + +Modernise each test so it uses the framework's `CodeQuery` syntax-lookup API and the TUnit assertion +extensions, and add a stage-by-stage cache test proving each incremental pipeline layer caches correctly. + +### Requirements + +1. Choose the correct base class and method for the component type: + - Generator → `TUnitSourceGeneratorTestBase` → `GenerateAsync`. + - Analyzer → `TUnitDiagnosticAnalyzerTestBase` → `AnalyzeAsync`. + - Code fix → `TUnitCodeFixTestBase` → `ApplyCodeFixAsync` / `ApplyFixAllAsync`. + - Refactor → `TUnitRefactoringTestBase` → `RefactorAsync`. +2. Replace `GetGeneratedTree(...)` + `string.Contains(...)` assertions with `CodeQuery` + (`result.Generated().Get/Has/TryGet…`) and the terminal assertion extensions + (`await Assert.That(result).HasGeneratedMethod/Class/Property/Field/SyntaxTree(…)`) that return the node. +3. Replace signature string checks with `TypeReference` parameter/return-type matching. +4. Ensure options come from a derived `SourceGeneratorTestOptions` record seeding the required namespaces + and additional assemblies; remove per-test duplication. +5. Add an incremental cache test using `RunIncrementalAsync` (or `GenerateIncrementalAsync` on the TUnit + base) with the four scenarios from the skills' "Incremental cache testing" sections + (`ServiceRegistrationCacheTests` / `IncrementalPipelineCacheTests` are the reference pattern): + - first run → every framework stage `New`; + - identical rerun (`RunIncrementalAsync(sources, …)` runs the same source twice) → framework stages + `Cached`/`Unchanged`; + - source-only change → `ForAttribute_*` `Modified`, property/config stages stay `Cached`; + - property-only change (`new IncrementalRunInput(sources, [("build_property.X", "value")])`) → + `GetMSBuildPropertyValue_*`/`GetGenerationConfiguration`/`GetGenerationContext_*` `Modified`, + `ForAttribute_*` stays `Cached`. + Use the `StepReasons(IncrementalCacheRun)` flattening helper; if the generator depends on its own + post-init output, assert on the framework-named stages rather than every tracked step. +6. Keep changes minimal and behavior equivalent; do not reformat unrelated tests. + +Verify by building the test project and running its suite before finishing. \ No newline at end of file diff --git a/.agents/skills/sdk-configuration-reference/SKILL.md b/.agents/skills/sdk-configuration-reference/SKILL.md index 25e0657f..7e60ecbd 100644 --- a/.agents/skills/sdk-configuration-reference/SKILL.md +++ b/.agents/skills/sdk-configuration-reference/SKILL.md @@ -44,7 +44,7 @@ These are the most important configurable properties exposed by the SDK: - `NamespacePrefix` — required unless `DisableNamespacePrefixCheck=true` - `DisableNamespacePrefixCheck` — default `false` - `TargetFramework` — defaults to `net10.0` when neither `TargetFramework` nor `TargetFrameworks` is set; projects explicitly declaring `IsRoslynComponent=true` default to `netstandard2.0` -- `IsRoslynComponent` — when explicitly `true`, applies source-generator defaults: a single `netstandard2.0` target, extended analyzer rules, disabled SourceLink and untracked-source embedding, no dependency file, compiler-generated output under the framework-specific intermediate directory, `symbols.nupkg`, `PackSourceGeneratorSymbols`, telemetry exclusion, and excluded normal build output +- `IsRoslynComponent` — when explicitly `true`, applies source-generator defaults: a single `netstandard2.0` target, `LangVersion=latest`, `Nullable=enable`, `TreatWarningsAsErrors=true`, extended analyzer rules, disabled SourceLink and untracked-source embedding, no dependency file, compiler-generated output under the framework-specific intermediate directory, telemetry exclusion, excluded normal build output, `IncludeSymbols=false` (no `.symbols.nupkg`/`.snupkg` by default — the PDB ships in `analyzers/dotnet/cs/`), and `PrivateAssets=all` applied to `Microsoft.CodeAnalysis.*` / `Microsoft.CodeAnalysis.Analyzers` references. Packable Roslyn components automatically pack the built analyzer assembly and PDB into `analyzers/dotnet/cs/` - `PackProjectReferencedSourceGenerators` — default `true`; packable projects automatically include analyzer `ProjectReference` outputs and runtime dependencies under `analyzers/dotnet/cs/`. Set it to `false` globally or use `Pack="false"` on one analyzer reference to opt out. - `EnableAssemblyNameGeneration` — default `true`; when `true`, `AssemblyName` and default `PackageId` follow the fully evaluated `RootNamespace`. Set `false` before the SDK import to use the standard project-name behaviour - `DisableProjectFileNamingConventionCheck` — default `false`; disables the directory-name/file-name match validation @@ -60,7 +60,7 @@ These are the most important configurable properties exposed by the SDK: - `ExcludeMSTelemetryExtension` — default `false`; removes `Microsoft.Extensions.Telemetry.Abstractions`, only relevant if `ExcludePurviewTelemetry` is also `true` - `IsPackable` — defaults to `false` if not set elsewhere - `PackageTags`, `IncludeSource`, `IncludeSymbols`, `PublishRepositoryUrl`, `SymbolPackageFormat` — standard pack-related settings the SDK participates in for packable projects -- Packable-project defaults (only applied when the consuming project has not supplied a value): `GenerateDocumentationFile=true`, `IncludeSymbols=true`, `SymbolPackageFormat=snupkg`, `PublishRepositoryUrl=true`, `EmbedUntrackedSources=true`, `DebugType=portable`. Portable PDBs are delivered through the `.snupkg`; the normal `.nupkg` does not receive PDB files unless the project opts in explicitly +- Packable-project defaults (only applied when the consuming project has not supplied a value): `GenerateDocumentationFile=true`, `IncludeSymbols=true`, `SymbolPackageFormat=snupkg`, `PublishRepositoryUrl=true`, `EmbedUntrackedSources=true`, `DebugType=portable`. Portable PDBs are delivered through the `.snupkg`; the normal `.nupkg` does not receive PDB files unless the project opts in explicitly. Roslyn components default `IncludeSymbols=false` and ship their PDB inside `analyzers/dotnet/cs/` instead - If the repo root is discoverable, the repository-root `README.md` is packed automatically (and registered via `PackageReadmeFile`) when the file exists and `PackageReadmeFile` was not configured explicitly ## Test framework settings diff --git a/.agents/skills/sdk-project-behavior-and-detection/SKILL.md b/.agents/skills/sdk-project-behavior-and-detection/SKILL.md index 23a0001f..bb275831 100644 --- a/.agents/skills/sdk-project-behavior-and-detection/SKILL.md +++ b/.agents/skills/sdk-project-behavior-and-detection/SKILL.md @@ -145,6 +145,13 @@ This is why consistent naming and placement matter so much in repos that use the - Packs the repository-root `README.md` (registered via `PackageReadmeFile`) when the file exists and `PackageReadmeFile` is unset; skips when a README is already being packed - Non-packable projects (including web apps) default `WarnOnPackingNonPackableProject=false` so solution-wide pack operations skip them silently +### For Roslyn component (analyzer/source-generator) projects + +- Defaults a single `netstandard2.0` target, `LangVersion=latest`, `Nullable=enable`, `TreatWarningsAsErrors=true`, extended analyzer rules, disabled SourceLink, and excluded normal build output (`IncludeBuildOutput=false`) +- Defaults `IncludeSymbols=false` — no `.symbols.nupkg` or `.snupkg` is produced by default; the analyzer PDB ships inside the main `.nupkg` under `analyzers/dotnet/cs/` beside the analyzer assembly +- Packable Roslyn components automatically pack the built analyzer assembly (and PDB) into `analyzers/dotnet/cs/`; `SymbolPackageFormat` defaults to the modern `snupkg` if symbols are explicitly opted into +- `Microsoft.CodeAnalysis.*` and `Microsoft.CodeAnalysis.Analyzers` references are defaulted to `PrivateAssets=all` (development-only dependencies) so they never leak into the packed nuspec + ### For test and shared-testing projects - Applies test-friendly `NoWarn` defaults diff --git a/.agents/skills/source-generator-testing/.gitignore b/.agents/skills/source-generator-testing/.gitignore new file mode 100644 index 00000000..27997545 --- /dev/null +++ b/.agents/skills/source-generator-testing/.gitignore @@ -0,0 +1,8 @@ +# Ignore all files +* + +# Don't ignore directories, so Git can traverse them +!*/ + +# Keep this file +!.gitignore \ No newline at end of file diff --git a/.agents/skills/tunit-test-authoring/.gitignore b/.agents/skills/tunit-test-authoring/.gitignore new file mode 100644 index 00000000..27997545 --- /dev/null +++ b/.agents/skills/tunit-test-authoring/.gitignore @@ -0,0 +1,8 @@ +# Ignore all files +* + +# Don't ignore directories, so Git can traverse them +!*/ + +# Keep this file +!.gitignore \ No newline at end of file diff --git a/.changeset/README.md b/.changeset/README.md deleted file mode 100644 index a4818be1..00000000 --- a/.changeset/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Changesets - -Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works -with multi-package repos, or single-package repos to help you version and publish your code. You can find the -full documentation for it [in our repository](https://github.com/changesets/changesets). - -We have a quick list of common questions to get you started engaging with this project in -[our docs](https://github.com/changesets/changesets/blob/main/docs/common-questions.md). We also have a -[introductory guide](https://github.com/changesets/changesets/blob/main/docs/intro-to-using-changesets.md) -and information about how we [version things](https://github.com/changesets/changesets/blob/main/docs/versioning-policy.md) -for further context. diff --git a/.changeset/config.json b/.changeset/config.json deleted file mode 100644 index 852554ea..00000000 --- a/.changeset/config.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", - "changelog": [ - "@changesets/changelog-github", - { "repo": "kjldev/purview-telemetry-sourcegenerator" } - ], - "commit": false, - "fixed": [], - "linked": [], - "access": "public", - "baseBranch": "main", - "updateInternalDependencies": "patch", - "ignore": [] -} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index a66914ee..b5aba8e8 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -26,3 +26,42 @@ jobs: - name: Run PR pipeline run: dotnet run --project build/PipelineCLI/PipelineCLI.csproj --configuration Release + + samples: + name: Build and test samples + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Setup .NET + uses: actions/setup-dotnet@v6 + with: + dotnet-version: "10.0.x" + + - name: Build SampleApp + run: dotnet build samples/SampleApp/SampleApp.slnx --configuration Release + + - name: Test SampleApp + run: dotnet test samples/SampleApp/SampleApp.slnx --configuration Release --no-build + + samples-net48: + name: Build SampleApp.Net48 + runs-on: windows-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Setup .NET + uses: actions/setup-dotnet@v6 + with: + dotnet-version: "10.0.x" + + - name: Build SampleApp.Net48 + run: dotnet build samples/SampleApp.Net48/SampleApp.Net48.slnx --configuration Release diff --git a/Directory.Packages.props b/Directory.Packages.props index 261f11fd..3d0f552c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,7 @@ 4.14.0 1.65.51 1.0.0-prerelease.28 - [3.2.8,) + 3.2.8 @@ -28,13 +28,33 @@ - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/global.json b/global.json index 6a87e31b..99327e7c 100644 --- a/global.json +++ b/global.json @@ -5,7 +5,8 @@ "allowPrerelease": false }, "msbuild-sdks": { - "Purview.DotNetProjectSdk": "1.0.0-prerelease.44" + "Purview.DotNetProjectSdk": "1.0.0-prerelease.45", + "Aspire.AppHost.Sdk": "13.2.0" }, "test": { "runner": "Microsoft.Testing.Platform" diff --git a/package.json b/package.json index 96d4587e..dad549c2 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,8 @@ { "name": "purview-telemetry-sourcegenerator", - "version": "5.0.0-prerelease.1", + "version": "5.0.0-prerelease.2", "description": "Generates [`ActivitySource`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitysource), [`ILogger`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.ilogger), and [`Metrics`](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.metrics) based on interface methods.", "readme": "README.md", - "scripts": { - "changeset": "changeset", - "version-packages": "changeset version && bun .build/update-version.ts" - }, "repository": { "type": "git", "url": "git+https://github.com/purview-dev/telemetry-sourcegenerator.git" @@ -16,9 +12,5 @@ "bugs": { "url": "https://github.com/purview-dev/telemetry-sourcegenerator/issues" }, - "homepage": "https://github.com/purview-dev/telemetry-sourcegenerator#readme", - "devDependencies": { - "@changesets/changelog-github": "^0.5.0", - "@changesets/cli": "^2.27.12" - } + "homepage": "https://github.com/purview-dev/telemetry-sourcegenerator#readme" } diff --git a/samples/Directory.Build.targets b/samples/Directory.Build.targets new file mode 100644 index 00000000..a3bbd31a --- /dev/null +++ b/samples/Directory.Build.targets @@ -0,0 +1,3 @@ + + + diff --git a/samples/SampleApp.Net48/Directory.Build.props b/samples/SampleApp.Net48/Directory.Build.props index 90461460..abbf5ef2 100644 --- a/samples/SampleApp.Net48/Directory.Build.props +++ b/samples/SampleApp.Net48/Directory.Build.props @@ -1,20 +1,29 @@ net48 - latest - disable - true - true - AllEnabledByDefault - latest + SampleApp.Net48 + + true + + $(DefineConstants);PURVIEW_TELEMETRY_ATTRIBUTES - $(NoWarn);CA1031;CA1303;CA1515;CA1812;CA2007;CA2201;CA5394;CS1574;TSG3022; + $(NoWarn);CA1031;CA1303;CA1515;CA1812;CA2007;CA2201;CA5394;CS1574;CS8618;CS8625;TSG3022; + + diff --git a/samples/SampleApp.Net48/README.md b/samples/SampleApp.Net48/README.md index d8eda90d..65f5204a 100644 --- a/samples/SampleApp.Net48/README.md +++ b/samples/SampleApp.Net48/README.md @@ -38,24 +38,21 @@ Meter : sample-weather-app-net48 ## How NET48 affects template generation The source generator injects attribute definition files (e.g. `ErrorAttribute.g.cs`) into the -consuming project. These files contain `#if` guards so they compile cleanly on **both** modern -.NET and .NET Framework 4.8: +consuming project. Because `NET48_OR_GREATER` is automatically defined by the SDK when targeting +`net48`, the injected member declarations use plain `string` instead of `string?`: ```csharp // In the injected ErrorAttribute.g.cs: -#if !NET48_OR_GREATER && !PURVIEW_TELEMETRY_NON_NULLABLE -#nullable enable -#endif - #if NET48_OR_GREATER || PURVIEW_TELEMETRY_NON_NULLABLE -public ErrorAttribute(string messageTemplate = null, string name = null) +public string MessageTemplate { get; set; } #else -public ErrorAttribute(string? messageTemplate = null, string? name = null) +public string? MessageTemplate { get; set; } #endif ``` -On net48, `NET48_OR_GREATER` is defined → the non-nullable branch is used, `#nullable enable` is -omitted, and the file compiles without requiring C# 8 nullable reference type support. +The attribute files are decorated with `[Conditional("PURVIEW_TELEMETRY_ATTRIBUTES")]`, so the +sample defines `PURVIEW_TELEMETRY_ATTRIBUTES` in `Directory.Build.props` to keep the attribute +usages visible to the generator's incremental pipeline. ### Opt-out for any project diff --git a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Program.cs b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Program.cs index a32306bd..f9f9bd8c 100644 --- a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Program.cs +++ b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Program.cs @@ -1,7 +1,5 @@ -using System; using System.Diagnostics; using System.Diagnostics.Metrics; -using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using SampleApp.Net48.ConsoleApp.Services; @@ -15,10 +13,8 @@ static void Main() using var activityListener = new ActivityListener { ShouldListenTo = source => source.Name == TelemetryNames.ActivitySourceNames[0], - Sample = (ref ActivityCreationOptions options) => - ActivitySamplingResult.AllDataAndRecorded, - SampleUsingParentId = (ref ActivityCreationOptions options) => - ActivitySamplingResult.AllDataAndRecorded, + Sample = (ref options) => ActivitySamplingResult.AllDataAndRecorded, + SampleUsingParentId = (ref options) => ActivitySamplingResult.AllDataAndRecorded, ActivityStarted = a => Console.WriteLine($" [TRACE START] {a.DisplayName} ({a.Id})"), ActivityStopped = a => Console.WriteLine( diff --git a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/SampleApp.Net48.ConsoleApp.csproj b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/SampleApp.Net48.ConsoleApp.csproj index 5e55f2ba..ff5bb01e 100644 --- a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/SampleApp.Net48.ConsoleApp.csproj +++ b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/SampleApp.Net48.ConsoleApp.csproj @@ -2,22 +2,28 @@ Exe true - true - $(NoWarn);CS1591 - + + + + + - - - + + + diff --git a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/IWeatherService.cs b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/IWeatherService.cs index 131745b4..0cbe719e 100644 --- a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/IWeatherService.cs +++ b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/IWeatherService.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace SampleApp.Net48.ConsoleApp.Services { public interface IWeatherService diff --git a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/IWeatherServiceTelemetry.cs b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/IWeatherServiceTelemetry.cs index 9d32ca9c..42b2b299 100644 --- a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/IWeatherServiceTelemetry.cs +++ b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/IWeatherServiceTelemetry.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics; using Purview.Telemetry; diff --git a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/WeatherService.cs b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/WeatherService.cs index c959c896..e31dedea 100644 --- a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/WeatherService.cs +++ b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/Services/WeatherService.cs @@ -1,13 +1,10 @@ -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; namespace SampleApp.Net48.ConsoleApp.Services { sealed class WeatherService : IWeatherService { - static readonly Random _rng = new Random(); + static readonly Random _rng = new(); static readonly string[] Summaries = [ @@ -37,7 +34,7 @@ public IReadOnlyList GetWeatherForecasts(int requestCount) const int minRequestCount = 5; const int maxRequestCount = 20; - if (requestCount < minRequestCount || requestCount > maxRequestCount) + if (requestCount is < minRequestCount or > maxRequestCount) { // MULTI-TARGET: This single call logs error + increments counter _telemetry.RequestedCountIsOutOfRange(requestCount); diff --git a/samples/SampleApp.Net48/SampleApp.Net48.slnx b/samples/SampleApp.Net48/SampleApp.Net48.slnx index 820a19b7..2af29d4e 100644 --- a/samples/SampleApp.Net48/SampleApp.Net48.slnx +++ b/samples/SampleApp.Net48/SampleApp.Net48.slnx @@ -1,6 +1,7 @@ + diff --git a/samples/SampleApp/Directory.Build.props b/samples/SampleApp/Directory.Build.props index 59db57fd..9b7237b2 100644 --- a/samples/SampleApp/Directory.Build.props +++ b/samples/SampleApp/Directory.Build.props @@ -1,13 +1,24 @@ - net10.0 - 14.0 - enable - enable - true - true - AllEnabledByDefault - latest - $(NoWarn);CA1031;CA1515;CA1724;CA2007;CA2201; + SampleApp + + true + + $(NoWarn);CA1031;CA1515;CA1724;CA2007;CA2201;CS8618;CS8625;TSG2008; + + diff --git a/samples/SampleApp/README.md b/samples/SampleApp/README.md index bca12a99..35345f2b 100644 --- a/samples/SampleApp/README.md +++ b/samples/SampleApp/README.md @@ -117,7 +117,7 @@ public interface IWeatherAPIClientTelemetry [Error] [AutoCounter] void FailedToGetForecast(Activity? activity, Exception ex, - [ExcludeTargets(Targets.Activities)] int? count); + [ExcludeTargets(TargetsEnum.Activities)] int? count); // SINGLE-TARGET: adds ActivityEvent with HTTP status details [Event] @@ -137,7 +137,7 @@ public interface IWeatherAPIClientTelemetry [Event(ActivityStatusCode.Ok)] [Debug] void ForecastsRecieved(Activity? activity, int forecastCount, - [ExpandEnumerable(100), ExcludeTargets(Targets.Activities)] WeatherForecast[] weatherForecasts); + [ExpandEnumerable(100), ExcludeTargets(TargetsEnum.Activities)] WeatherForecast[] weatherForecasts); } ``` @@ -188,11 +188,8 @@ SampleApp/ ### Prerequisites - [.NET 10.0 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) or later -- [.NET Aspire workload](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/setup-tooling) - -```bash -dotnet workload install aspire -``` +- [Aspire CLI](https://learn.microsoft.com/en-us/dotnet/aspire/) — the AppHost SDK (`Aspire.AppHost.Sdk`) and + orchestration binaries are resolved from NuGet and the Aspire CLI bundle, so no legacy Aspire workload is required. ### Running the Application diff --git a/samples/SampleApp/SampleApp.APIService.UnitTests/SampleApp.APIService.UnitTests.csproj b/samples/SampleApp/SampleApp.APIService.UnitTests/SampleApp.APIService.UnitTests.csproj index 1f0c7ab0..f15a48f6 100644 --- a/samples/SampleApp/SampleApp.APIService.UnitTests/SampleApp.APIService.UnitTests.csproj +++ b/samples/SampleApp/SampleApp.APIService.UnitTests/SampleApp.APIService.UnitTests.csproj @@ -1,26 +1,5 @@  - - Exe - $(NoWarn);CA1707; - $(NoWarn);IDE0039; - true - true - true - true - [NSubstitute*]*,[TUnit.*]*,[Microsoft.Testing.*]*,[Microsoft.NET.Test*] - SampleApp - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - diff --git a/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Failure.cs b/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Failure.cs index 895aa9ae..540d4988 100644 --- a/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Failure.cs +++ b/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Failure.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; namespace SampleApp.APIService.Services; @@ -18,7 +18,7 @@ CancellationToken cancellationToken nameof(GetWeatherForecastsAsync_GivenSimulatedUpstreamFails_CallsFailureActivityEventAndLog) ); - telemetry.GettingWeatherForecast(Arg.Any(), requestCount).Returns(activity); + telemetry.GettingWeatherForecast(Any(), Is(requestCount)).Returns(activity); // Act var err = await service.GetWeatherForecastsAsync(requestCount, cancellationToken); @@ -28,6 +28,6 @@ CancellationToken cancellationToken await Assert.That(err.FirstError.Code).IsEqualTo("WeatherForecast.RetrievalFailed"); await Assert.That(err.FirstError.Type).IsEqualTo(ErrorOr.ErrorType.Failure); - telemetry.Received(1).FailedToRetrieveForecast(Arg.Is(activity), Arg.Any()); + telemetry.FailedToRetrieveForecast(Is(activity), Any()).WasCalled(Times.Once); } } diff --git a/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Success.cs b/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Success.cs index 0e0228ef..dd424c32 100644 --- a/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Success.cs +++ b/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Success.cs @@ -1,4 +1,4 @@ -namespace SampleApp.APIService.Services; +namespace SampleApp.APIService.Services; partial class WeatherServiceTests { @@ -19,6 +19,6 @@ CancellationToken cancellationToken await service.GetWeatherForecastsAsync(requestCount, cancellationToken); // Assert - telemetry.Received(1).GettingWeatherForecast(Arg.Any(), Arg.Is(requestCount)); + telemetry.GettingWeatherForecast(Any(), Is(requestCount)).WasCalled(Times.Once); } } diff --git a/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Validation.cs b/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Validation.cs index b2d1c6fa..90b7a3c3 100644 --- a/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Validation.cs +++ b/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.Validation.cs @@ -1,4 +1,4 @@ -namespace SampleApp.APIService.Services; +namespace SampleApp.APIService.Services; partial class WeatherServiceTests { @@ -24,6 +24,6 @@ CancellationToken cancellationToken await Assert.That(err.FirstError.Code).IsEqualTo("RequestCount.Invalid"); await Assert.That(err.FirstError.Type).IsEqualTo(ErrorOr.ErrorType.Validation); - telemetry.Received(1).RequestedCountIsOutOfRange(Arg.Is(requestCount)); + telemetry.RequestedCountIsOutOfRange(Is(requestCount)).WasCalled(Times.Once); } } diff --git a/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.cs b/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.cs index 41644c2d..1d72e80a 100644 --- a/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.cs +++ b/samples/SampleApp/SampleApp.APIService.UnitTests/Services/WeatherServiceTests.cs @@ -2,11 +2,11 @@ public partial class WeatherServiceTests { - static WeatherService CreateService(IWeatherServiceTelemetry? telemetry, bool throwOnRNG = false) => + static WeatherService CreateService(Mock? telemetry, bool throwOnRNG = false) => new( - telemetry: telemetry ?? CreateTelemetry(), + telemetry: (telemetry ?? Mock.Of()).Object, rng: () => throwOnRNG ? 8 : 1 // 8 is out magic eight-ball number - it throws randomly in simulated use. ); - static IWeatherServiceTelemetry CreateTelemetry() => Substitute.For(); + static Mock CreateTelemetry() => Mock.Of(); } diff --git a/samples/SampleApp/SampleApp.APIService/Endpoints/WeatherEndpoints.cs b/samples/SampleApp/SampleApp.APIService/Endpoints/WeatherEndpoints.cs index a857f01d..85b78028 100644 --- a/samples/SampleApp/SampleApp.APIService/Endpoints/WeatherEndpoints.cs +++ b/samples/SampleApp/SampleApp.APIService/Endpoints/WeatherEndpoints.cs @@ -65,6 +65,7 @@ ErrorOr> results if (results.IsError) return TypedResults.Problem(results.FirstError.ToProblemDetails()); + // If the results are empty, return NoContent; otherwise, return Ok with the results. return results.Value?.Any() == true ? TypedResults.Ok(results.Value.ToArray()) : TypedResults.NoContent(); } } diff --git a/samples/SampleApp/SampleApp.APIService/Program.cs b/samples/SampleApp/SampleApp.APIService/Program.cs index d503aa48..6f79cb7c 100644 --- a/samples/SampleApp/SampleApp.APIService/Program.cs +++ b/samples/SampleApp/SampleApp.APIService/Program.cs @@ -1,4 +1,3 @@ -using SampleApp.APIService; using SampleApp.APIService.Endpoints; using SampleApp.APIService.Services; diff --git a/samples/SampleApp/SampleApp.APIService/SampleApp.APIService.csproj b/samples/SampleApp/SampleApp.APIService/SampleApp.APIService.csproj index 383c9fff..e8bd0b6a 100644 --- a/samples/SampleApp/SampleApp.APIService/SampleApp.APIService.csproj +++ b/samples/SampleApp/SampleApp.APIService/SampleApp.APIService.csproj @@ -1,18 +1,21 @@  true - true - $(NoWarn);CS1591 - + + analyzer only propagates the generator assembly, so ship the generator and its runtime + beside it explicitly. The framework package ships analyzer + library assets, so only + the generator and the two runtime DLLs are referenced here. --> + @@ -20,11 +23,5 @@ - - <_Parameter1>DynamicProxyGenAssembly2 - - - <_Parameter1>SampleApp.APIService.UnitTests - diff --git a/samples/SampleApp/SampleApp.APIService/Services/IWeatherServiceTelemetry.cs b/samples/SampleApp/SampleApp.APIService/Services/IWeatherServiceTelemetry.cs index ecc54144..20598bfe 100644 --- a/samples/SampleApp/SampleApp.APIService/Services/IWeatherServiceTelemetry.cs +++ b/samples/SampleApp/SampleApp.APIService/Services/IWeatherServiceTelemetry.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Purview.Telemetry; namespace SampleApp.APIService.Services; @@ -27,7 +27,7 @@ public interface IWeatherServiceTelemetry // --> SINGLE-TARGET: Event (Error) [Event(ActivityStatusCode.Error)] - void FailedToRetrieveForecast(Activity? activity, Exception ex); + void FailedToRetrieveForecast(Activity? activity, Exception exception); // --> SINGLE-TARGET: Event (Ok) [Event(ActivityStatusCode.Ok)] diff --git a/samples/SampleApp/SampleApp.APIService/Services/WeatherService.cs b/samples/SampleApp/SampleApp.APIService/Services/WeatherService.cs index 7edd53d8..29c107b2 100644 --- a/samples/SampleApp/SampleApp.APIService/Services/WeatherService.cs +++ b/samples/SampleApp/SampleApp.APIService/Services/WeatherService.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Security.Cryptography; namespace SampleApp.APIService.Services; @@ -33,7 +33,7 @@ public async Task>> GetWeatherForecastsAsyn const int minRequestCount = 5; const int maxRequestCount = 20; - if (requestCount < minRequestCount || requestCount > maxRequestCount) + if (requestCount is < minRequestCount or > maxRequestCount) { telemetry.RequestedCountIsOutOfRange(requestCount); diff --git a/samples/SampleApp/SampleApp.AppHost/SampleApp.AppHost.csproj b/samples/SampleApp/SampleApp.AppHost/SampleApp.AppHost.csproj index aba5ed1a..89db1d71 100644 --- a/samples/SampleApp/SampleApp.AppHost/SampleApp.AppHost.csproj +++ b/samples/SampleApp/SampleApp.AppHost/SampleApp.AppHost.csproj @@ -1,9 +1,16 @@ - Exe - true + + false + + $(NoWarn);ASPIRE010; + + diff --git a/samples/SampleApp/SampleApp.ServiceDefaults/SampleApp.ServiceDefaults.csproj b/samples/SampleApp/SampleApp.ServiceDefaults/SampleApp.ServiceDefaults.csproj index 88e96442..3c0c937d 100644 --- a/samples/SampleApp/SampleApp.ServiceDefaults/SampleApp.ServiceDefaults.csproj +++ b/samples/SampleApp/SampleApp.ServiceDefaults/SampleApp.ServiceDefaults.csproj @@ -1,23 +1,36 @@  - true - $(NoWarn);CA1034;IDE0130; + + SampleApp.ServiceDefaults + SampleApp.ServiceDefaults + + false + + $(NoWarn);CA1034;IDE0130;AV0027;AV0029;AV0030; - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/samples/SampleApp/SampleApp.ServiceDefaults/WebApplicationExtensions.cs b/samples/SampleApp/SampleApp.ServiceDefaults/WebApplicationExtensions.cs index c6ca57bb..c45ef28b 100644 --- a/samples/SampleApp/SampleApp.ServiceDefaults/WebApplicationExtensions.cs +++ b/samples/SampleApp/SampleApp.ServiceDefaults/WebApplicationExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; @@ -34,7 +34,7 @@ void UseDefaultOpenAPI() if (!openApiSection.Exists()) return; - app.MapOpenApi(); + app.MapOpenApi("v1"); if (app.Environment.IsDevelopment()) { diff --git a/samples/SampleApp/SampleApp.Shared/SampleApp.Shared.csproj b/samples/SampleApp/SampleApp.Shared/SampleApp.Shared.csproj index 63839088..2ef1a363 100644 --- a/samples/SampleApp/SampleApp.Shared/SampleApp.Shared.csproj +++ b/samples/SampleApp/SampleApp.Shared/SampleApp.Shared.csproj @@ -1,5 +1 @@ - - - SampleApp - - + diff --git a/samples/SampleApp/SampleApp.Web/Clients/IWeatherAPIClientTelemetry.cs b/samples/SampleApp/SampleApp.Web/Clients/IWeatherAPIClientTelemetry.cs index cdc3dc93..beca8b5d 100644 --- a/samples/SampleApp/SampleApp.Web/Clients/IWeatherAPIClientTelemetry.cs +++ b/samples/SampleApp/SampleApp.Web/Clients/IWeatherAPIClientTelemetry.cs @@ -17,7 +17,7 @@ public interface IWeatherAPIClientTelemetry [Event] [Error] [AutoCounter] - void FailedToGetForecast(Activity? activity, Exception ex, [ExcludeTargets(Targets.Activities)] int? count); + void FailedToGetForecast(Activity? activity, Exception ex, [ExcludeTargets(TargetsEnum.Activities)] int? count); [Event] void RequestComplete(Activity? activity, HttpStatusCode statusCode, bool isSuccessStatusCode); @@ -34,7 +34,7 @@ public interface IWeatherAPIClientTelemetry void ForecastsRecieved( Activity? activity, int forecastCount, - [ExpandEnumerable(100), ExcludeTargets(Targets.Activities)] + [ExpandEnumerable(100), ExcludeTargets(TargetsEnum.Activities)] #pragma warning disable TSG2008 // Unbounded enumeration possible WeatherForecast[] weatherForecasts ); diff --git a/samples/SampleApp/SampleApp.Web/Program.cs b/samples/SampleApp/SampleApp.Web/Program.cs index 3ff494bb..7696bb3b 100644 --- a/samples/SampleApp/SampleApp.Web/Program.cs +++ b/samples/SampleApp/SampleApp.Web/Program.cs @@ -1,4 +1,3 @@ -using SampleApp.Web; using SampleApp.Web.Clients; using SampleApp.Web.Components; diff --git a/samples/SampleApp/SampleApp.Web/SampleApp.Web.csproj b/samples/SampleApp/SampleApp.Web/SampleApp.Web.csproj index 40b3bce3..c8d7f42f 100644 --- a/samples/SampleApp/SampleApp.Web/SampleApp.Web.csproj +++ b/samples/SampleApp/SampleApp.Web/SampleApp.Web.csproj @@ -5,14 +5,19 @@ - + + analyzer only propagates the generator assembly, so ship the generator and its runtime + beside it explicitly. The framework package ships analyzer + library assets, so only + the generator and the two runtime DLLs are referenced here. --> + diff --git a/samples/SampleApp/SampleApp.slnx b/samples/SampleApp/SampleApp.slnx index 41a62674..331d4cd5 100644 --- a/samples/SampleApp/SampleApp.slnx +++ b/samples/SampleApp/SampleApp.slnx @@ -1,12 +1,17 @@ + - - - - - - + + + + + + + + + + diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 85c315a9..e4cca61d 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,15 +1,16 @@ Purview.Telemetry - false true - false - true + + + preview + diff --git a/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs b/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs index 2c08136d..f21a1308 100644 --- a/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs +++ b/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs @@ -954,6 +954,8 @@ static void WriteMeterGenerationAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Assembly, body => { + WriteEmptyConstructor(body, type); + body.WriteConstructor( new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) { diff --git a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs index cc3c6b34..736b2624 100644 --- a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs @@ -666,11 +666,12 @@ LoggerOutputContext output .Write(iteratorVarName) .Write(" == ") .Write(maxCount.ToString(CultureInfo.InvariantCulture)) - .WriteLine(")") - .Write("{"); - - using (snippet.OpenBlockScope()) - snippet.WriteLine("break;"); + .WriteLine(")"); + snippet.WriteLine("{"); + snippet.Indent(); + snippet.WriteLine("break;"); + snippet.Unindent(); + snippet.WriteLine("}"); snippet.NewLine(); @@ -678,7 +679,6 @@ LoggerOutputContext output snippet.Write(iteratorVarName).WriteLine("++;"); - snippet.Write("}"); snippet.Unindent(); snippet.Write("}"); snippet.Unindent(); diff --git a/src/src/SourceGenerator/SourceGenerator.csproj b/src/src/SourceGenerator/SourceGenerator.csproj index 594963d4..4dce14d2 100644 --- a/src/src/SourceGenerator/SourceGenerator.csproj +++ b/src/src/SourceGenerator/SourceGenerator.csproj @@ -5,15 +5,16 @@ true - true - 2026 + + Purview Telemetry Source Generator .NET Source Generator for interface-based telemetry generating tracing, logs, and metrics. purview-logo.jpg - Kieron Lanning + + https://github.com/purview-dev/telemetry-sourcegenerator/ LICENSE.md README.md diff --git a/src/tests/SourceGenerator.IntegrationTests/Analyzers/TelemetryDiagnosticAnalyzerTests.cs b/src/tests/SourceGenerator.IntegrationTests/Analyzers/TelemetryDiagnosticAnalyzerTests.cs new file mode 100644 index 00000000..ab3411c3 --- /dev/null +++ b/src/tests/SourceGenerator.IntegrationTests/Analyzers/TelemetryDiagnosticAnalyzerTests.cs @@ -0,0 +1,115 @@ +using Purview.Telemetry.SourceGenerator.Infra; + +namespace Purview.Telemetry.SourceGenerator.Analyzers; + +/// +/// Standalone analyzer tests that run directly through the +/// framework's TUnitDiagnosticAnalyzerTestBase, without executing the generator. The telemetry +/// attribute types are supplied by . +/// +public class TelemetryDiagnosticAnalyzerTests + : TUnitDiagnosticAnalyzerTestBase +{ + [Test] + public async Task Analyze_GivenGenericActivityInterface_RaisesGenericInterfaceDiagnostic( + CancellationToken cancellationToken + ) + { + const string code = """ + [ActivitySource] + public interface ITestActivities + { + } + """; + + var result = await AnalyzeAsync(code, new TelemetryAnalyzerTestOptions(), cancellationToken); + + await Assert.That(result).HasDiagnostic("TSG1004"); + } + + [Test] + public async Task Analyze_GivenDuplicateActivityMethodNames_RaisesDuplicateNameDiagnostic( + CancellationToken cancellationToken + ) + { + const string code = """ + + [ActivitySource] + public interface ITestActivities + { + [Activity] + System.Diagnostics.Activity? DoWork([Baggage]string stringParam); + + [Activity] + System.Diagnostics.Activity? DoWork([Tag]int intParam); + } + + """; + + var result = await AnalyzeAsync(code, new TelemetryAnalyzerTestOptions(), cancellationToken); + + await Assert.That(result).HasDiagnostic("TSG1003"); + } + + [Test] + public async Task Analyze_GivenLogMethodReturningString_RaisesInvalidReturnDiagnostic( + CancellationToken cancellationToken + ) + { + const string code = """ + + [Logger] + public interface ITestLogger + { + [Log] + string Log(string message); + } + + """; + + var result = await AnalyzeAsync(code, new TelemetryAnalyzerTestOptions(), cancellationToken); + + await Assert.That(result).HasDiagnostic("TSG2021"); + } + + [Test] + [SkipOnNetFramework] + public async Task Analyze_GivenMetricMethodReturningInt_RaisesInvalidReturnDiagnostic( + CancellationToken cancellationToken + ) + { + const string code = """ + + [Meter("testing-meter")] + public interface ITestMetrics + { + [Counter] + int InvalidReturnType(int value); + } + + """; + + var result = await AnalyzeAsync(code, new TelemetryAnalyzerTestOptions(), cancellationToken); + + await Assert.That(result).HasDiagnostic("TSG4001"); + } + + [Test] + public async Task Analyze_GivenValidActivityInterface_RaisesNoDiagnostics(CancellationToken cancellationToken) + { + const string code = """ + + [ActivitySource] + public interface ITestActivities + { + [Activity] + System.Diagnostics.Activity? DoWork([Baggage]string stringParam, [Tag]int intParam); + } + + """; + + var result = await AnalyzeAsync(code, new TelemetryAnalyzerTestOptions(), cancellationToken); + + await Assert.That(result).HasNoDiagnostics(); + } +} diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicContextGen.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicContextGen.cs index 7343b7ee..af159562 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicContextGen.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicContextGen.cs @@ -1,4 +1,5 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using System.Diagnostics; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Activities; @@ -28,7 +29,25 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity")) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That( + implClass.HasMethod( + query, + "Context", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the context method with its parameter signature"); } [Test] @@ -59,7 +78,25 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Context", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the context method"); + await Assert + .That(implClass.HasMethodReturnType(query, "Context", TypeReference.Create())) + .IsTrue() + .Because("the context method must return an Activity"); } [Test] @@ -92,7 +129,44 @@ public interface ITestActivities { var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Context", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the context method"); + await Assert + .That( + implClass.HasMethod( + query, + "ContextWithNullableReturnActivity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the nullable-return context method"); + await Assert + .That( + implClass.HasMethodReturnType( + query, + "ContextWithNullableReturnActivity", + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the nullable-return context method must return a nullable Activity"); } [Test] @@ -126,7 +200,34 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Context", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create().Nullable(GenerationSettings.Create()), + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the generated implementation must contain the context method with nullable parameters"); + await Assert + .That( + implClass.HasMethod( + query, + "ContextWithNullableParams", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create().Nullable(GenerationSettings.Create()), + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the generated implementation must contain the nullable-parameter context method"); } [Test] @@ -158,7 +259,34 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Context", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create().Nullable(GenerationSettings.Create()), + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the generated implementation must contain the context method"); + await Assert + .That( + implClass.HasMethod( + query, + "ContextWithNullableParams", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create().Nullable(GenerationSettings.Create()), + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the generated implementation must contain the nullable-activity context method"); } [Test] @@ -192,6 +320,33 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Context", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create().Nullable(GenerationSettings.Create()), + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the generated implementation must contain the void context method"); + await Assert + .That( + implClass.HasMethod( + query, + "ContextWithNullableParams", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create().Nullable(GenerationSettings.Create()), + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the generated implementation must contain the void nullable-activity context method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicEventGen.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicEventGen.cs index ca1fdc7a..39fffa6b 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicEventGen.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicEventGen.cs @@ -1,4 +1,5 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using System.Diagnostics; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Activities; @@ -36,7 +37,25 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity")) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That( + implClass.HasMethod( + query, + "ThisIsAMethod", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the inferred event method"); } [Test] @@ -65,7 +84,25 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity")) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That( + implClass.HasMethod( + query, + "Event", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the event method with its parameter signature"); } [Test] @@ -96,7 +133,25 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity")) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That( + implClass.HasMethod( + query, + "Event", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the event method with a nullable activity parameter"); } [Test] @@ -127,7 +182,16 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity")) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That(implClass.HasMethod(query, "Event", TypeReference.Create())) + .IsTrue() + .Because("the generated implementation must contain the event method"); } [Test] @@ -158,7 +222,16 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity")) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That(implClass.HasMethod(query, "Event", TypeReference.Create())) + .IsTrue() + .Because("the generated implementation must contain the error-status event method"); } [Test] @@ -254,6 +327,17 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Event", TypeReference.Create(), TypeReference.Create())) + .IsTrue() + .Because("the generated implementation must contain the first status-description event method"); + await Assert + .That( + implClass.HasMethod(query, "Event2", TypeReference.Create(), TypeReference.Create()) + ) + .IsTrue() + .Because("the generated implementation must contain the second status-description event method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicGen.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicGen.cs index 0aee2ae3..31844f87 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicGen.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.BasicGen.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Activities; @@ -28,7 +30,43 @@ public interface ITestActivities { var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method with its parameter signature"); + await Assert + .That( + implClass.HasMethodReturnType( + query, + "Activity", + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the activity method must return a nullable Activity"); + await Assert + .That( + implClass.HasMethod( + query, + "Event", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the event method with its parameter signature"); } [Test] @@ -87,7 +125,33 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That( + implClass.HasMethod( + query, + "Event", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the event method"); } [Test] @@ -216,7 +280,32 @@ public interface ITestActivities { var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create().Nullable(GenerationSettings.Create()), + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method with nullable parameters"); + await Assert + .That( + implClass.HasMethod( + query, + "ActivityWithNullableParams", + TypeReference.Create(), + TypeReference.Create().Nullable(GenerationSettings.Create()), + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the generated implementation must contain the nullable-parameter activity method"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.CSharp73.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.CSharp73.cs index 6d4c9249..13ffcca3 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.CSharp73.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.CSharp73.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Activities; @@ -34,10 +34,34 @@ public interface ITestActivities { // Assert: validates the generated code compiles. The source uses C# 7.3-style syntax // (block namespaces, no nullable annotations). TSG3022 (non-nullable Activity return // type) is a warning, so ignore non-errors. - await TestHelpers.VerifyAsync( - generationResult, - whenValidatingDiagnosticsIgnoreNonErrors: true, - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasNoErrorDiagnostics(); + + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That( + implClass.HasMethod( + query, + "Event", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the event method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.DI.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.DI.cs index 46b24cc0..387c656c 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.DI.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.DI.cs @@ -1,4 +1,6 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using System.Diagnostics; +using Microsoft.Extensions.DependencyInjection; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Activities; @@ -35,7 +37,37 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method with its parameter signature"); + await Assert + .That( + implClass.HasMethodReturnType( + query, + "Activity", + TypeReference.Create().Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because("the activity method must return a nullable Activity"); + + var diClass = query.GetClass("TestActivitiesCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestActivities", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must register the implementation via AddTestActivities"); } [Test] @@ -68,7 +100,25 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + var diClass = query.GetClass("TestActivitiesCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestActivities", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must register the implementation via AddTestActivities"); } [Test] @@ -105,7 +155,25 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + var diClass = query.GetClass("TestActivitiesCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestActivities", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must be generated when the interface opts in"); } [Test] @@ -142,7 +210,24 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That(query.HasClass("TestActivitiesCoreDIExtension", "Microsoft.Extensions.DependencyInjection")) + .IsFalse() + .Because("the DI extension must not be generated when the interface opts out"); } [Test] @@ -178,6 +263,24 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + var diClass = query.GetClass("TestActivitiesCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestActivities", TypeReference.Create())) + .IsTrue() + .Because("the public DI extension must register the implementation via AddTestActivities"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.ParentTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.ParentTests.cs index 5ad342ca..136d6fc6 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.ParentTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.ParentTests.cs @@ -1,4 +1,5 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using System.Diagnostics; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Activities; @@ -27,7 +28,12 @@ public interface ITestActivities { var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity", TypeReference.Create())) + .IsTrue() + .Because("the generated implementation must contain the activity method with an ActivityContext parameter"); } [Test] @@ -53,7 +59,22 @@ public interface ITestActivities { var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference + .Create() + .Nullable(GenerationSettings.Create()) + ) + ) + .IsTrue() + .Because( + "the generated implementation must contain the activity method with a nullable ActivityContext parameter" + ); } [Test] @@ -77,7 +98,12 @@ public interface ITestActivities { var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity", TypeReference.Create())) + .IsTrue() + .Because("the generated implementation must contain the activity method with a parent-id string parameter"); } [Test] @@ -103,6 +129,13 @@ public interface ITestActivities { var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity", TypeReference.Create())) + .IsTrue() + .Because( + "the generated implementation must contain the activity method with a nullable parent-id string parameter" + ); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.cs index 763c41cc..94c8db80 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorActivityTests.cs @@ -36,7 +36,20 @@ public interface ITestActivities var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That(implClass.HasMethod(query, "Activity")) + .IsTrue() + .Because("the generated implementation must contain the activity method"); + await Assert + .That(implClass.HasMethod(query, "Event")) + .IsTrue() + .Because("the generated implementation must contain the event method"); + await Assert + .That(implClass.HasMethod(query, "Context")) + .IsTrue() + .Because("the generated implementation must contain the context method"); } [Test] @@ -69,7 +82,7 @@ public interface ITestActivities<{genericTypeDef}> ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG1004"); } [Test] @@ -102,6 +115,6 @@ public interface ITestActivities ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG1005"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorCachingTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorCachingTests.cs index 3ac888ab..24313ab0 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorCachingTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorCachingTests.cs @@ -1,19 +1,18 @@ -using System.Diagnostics; -using System.Diagnostics.Metrics; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Generators; /// -/// Verifies the per-target incremental pipeline: an unrelated edit must leave all generated -/// output unchanged, and editing one target's interface must change only that target's output. +/// Verifies the incremental pipeline caches across unchanged runs and invalidates only the +/// affected target when an interface is edited. Runs through the framework's incremental harness +/// (GenerateIncrementalAsync) and asserts on the generator's tracking-named stages, per the +/// source-generator-testing guidance. /// -public class TelemetrySourceGeneratorCachingTests +public class TelemetrySourceGeneratorCachingTests : IncrementalSourceGeneratorTestBase { const string ActivityInterface = """ + using Purview.Telemetry; + namespace Testing; [ActivitySource("testing-activity-source")] @@ -26,6 +25,7 @@ public interface ITestActivities const string LoggerInterface = """ using Microsoft.Extensions.Logging; + using Purview.Telemetry; namespace Testing; @@ -42,129 +42,182 @@ namespace Testing; public class Unrelated { public int Value { get; set; } } """; - static ImmutableArray References { get; } = BuildReferences(); + static readonly TelemetrySourceGeneratorTestOptions Options = new(); + + /// + /// The framework-named pipeline stages this generator participates in. The generator overrides the + /// ForAttributeWithMetadataName tracking names (TelemetrySourceGenerator_*); the + /// remaining stages come from the framework's GenerationContextValueProvider + /// (GetGenerationContext_*). The generator passes a disable-property + /// name, so no GetMSBuildPropertyValue_* stages exist and a property-only invalidation test + /// is not applicable. + /// + static bool IsFrameworkStage(string trackingName) => + trackingName.StartsWith("TelemetrySourceGenerator_", StringComparison.Ordinal) + || trackingName.StartsWith("GetGenerationContext_", StringComparison.Ordinal); + + static ImmutableArray GetFrameworkReasons(IncrementalCacheRun run) => + [ + .. run + .Steps.Where(static kvp => IsFrameworkStage(kvp.Key)) + .SelectMany(static kvp => kvp.Value) + .SelectMany(static step => step.Outputs) + .Select(static output => output.Reason), + ]; + + static ImmutableArray GetStageReasons(IncrementalCacheRun run, string trackingName) + { + return + [ + .. run + .Steps.Where(kvp => kvp.Key == trackingName) + .SelectMany(kvp => kvp.Value) + .SelectMany(static step => step.Outputs) + .Select(static output => output.Reason), + ]; + } - static ImmutableArray BuildReferences() + static ImmutableDictionary GetGeneratedSources(GeneratorRunResult result) { - var trusted = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") ?? "") - .Split([Path.PathSeparator], StringSplitOptions.RemoveEmptyEntries) - .Select(path => MetadataReference.CreateFromFile(path)) - .ToList(); - - foreach ( - var type in new[] - { - typeof(Activity), - typeof(Meter), - typeof(IServiceCollection), - typeof(LogLevel), - typeof(LogPropertiesAttribute), - } - ) + var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); + foreach (var generated in result.GeneratedSources) { - trusted.Add(MetadataReference.CreateFromFile(type.Assembly.Location)); + if (builder.ContainsKey(generated.HintName)) + continue; + + builder.Add(generated.HintName, generated.SourceText.ToString()); } - return [.. trusted]; + return builder.ToImmutable(); } - static CSharpCompilation CreateCompilation(params string[] sources) + [Test] + public async Task Generate_UnchangedCompilation_OutputCached(CancellationToken cancellationToken) { - var trees = sources.Select(source => CSharpSyntaxTree.ParseText(source)); - return CSharpCompilation.Create( - "CachingTest", - trees, - References, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - ); + var result = await GenerateIncrementalAsync([ActivityInterface, LoggerInterface], Options, cancellationToken); + + await Assert + .That(GetFrameworkReasons(result.Runs[0]).All(static reason => reason == IncrementalStepRunReason.New)) + .IsTrue() + .Because("the first run must produce every output"); + await Assert + .That( + GetFrameworkReasons(result.Runs[1]) + .All(static reason => + reason is IncrementalStepRunReason.Cached or IncrementalStepRunReason.Unchanged + ) + ) + .IsTrue() + .Because("an unchanged compilation must not re-run any target"); } - static CSharpGeneratorDriver CreateDriver() => - CSharpGeneratorDriver.Create(generators: [new TelemetrySourceGenerator().AsSourceGenerator()]); - - static ImmutableDictionary GetGeneratedSources(GeneratorDriverRunResult result) + [Test] + public async Task Generate_UnchangedCompilation_OutputIsStable(CancellationToken cancellationToken) { - var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal); - foreach (var generated in result.Results.SelectMany(static r => r.GeneratedSources)) - { - var hintName = generated.HintName; - if (builder.ContainsKey(hintName)) - continue; + var result = await GenerateIncrementalAsync([ActivityInterface, LoggerInterface], Options, cancellationToken); - builder.Add(hintName, generated.SourceText.ToString()); - } + var first = GetGeneratedSources(result.Runs[0].RunResult); + var second = GetGeneratedSources(result.Runs[1].RunResult); - return builder.ToImmutable(); + await Assert.That(second.Count).IsEqualTo(first.Count); + foreach (var source in first) + await Assert.That(second[source.Key]).IsEqualTo(source.Value); } [Test] - public async Task Generate_UnchangedCompilation_OutputIsStable(CancellationToken cancellationToken) + public async Task Generate_UnrelatedChange_OutputStaysCached(CancellationToken cancellationToken) { - var compilation = CreateCompilation(ActivityInterface, LoggerInterface); - var driver = CreateDriver(); - - var first = driver - .RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken) - .GetRunResult(); - var firstSources = GetGeneratedSources(first); - - var second = driver - .RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken) - .GetRunResult(); - var secondSources = GetGeneratedSources(second); - - await Assert.That(secondSources.Count).IsEqualTo(firstSources.Count); - foreach (var source in firstSources) - await Assert.That(secondSources[source.Key]).IsEqualTo(source.Value); + var inputs = new[] + { + new IncrementalRunInput([ActivityInterface, LoggerInterface], []), + new IncrementalRunInput([ActivityInterface, LoggerInterface, UnrelatedType], []), + }; + var result = await GenerateIncrementalAsync(inputs, Options, cancellationToken); + + var reasons = GetFrameworkReasons(result.Runs[1]); + await Assert.That(reasons).IsNotEmpty(); + await Assert + .That( + reasons.All(static reason => + reason is IncrementalStepRunReason.Cached or IncrementalStepRunReason.Unchanged + ) + ) + .IsTrue() + .Because("an unrelated edit must not invalidate the generated output"); } [Test] public async Task Generate_UnrelatedChange_AllOutputUnchanged(CancellationToken cancellationToken) { - var compilation = CreateCompilation(ActivityInterface, LoggerInterface); - var driver = CreateDriver(); - var first = driver - .RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken) - .GetRunResult(); - var firstSources = GetGeneratedSources(first); - - var changed = CreateCompilation(ActivityInterface, LoggerInterface, UnrelatedType); - var run = driver.RunGeneratorsAndUpdateCompilation(changed, out _, out _, cancellationToken).GetRunResult(); - var runSources = GetGeneratedSources(run); - - await Assert.That(runSources.Count).IsEqualTo(firstSources.Count); - foreach (var source in firstSources) - await Assert.That(runSources[source.Key]).IsEqualTo(source.Value); + var inputs = new[] + { + new IncrementalRunInput([ActivityInterface, LoggerInterface], []), + new IncrementalRunInput([ActivityInterface, LoggerInterface, UnrelatedType], []), + }; + var result = await GenerateIncrementalAsync(inputs, Options, cancellationToken); + + var first = GetGeneratedSources(result.Runs[0].RunResult); + var second = GetGeneratedSources(result.Runs[1].RunResult); + + await Assert.That(second.Count).IsEqualTo(first.Count); + foreach (var source in first) + await Assert.That(second[source.Key]).IsEqualTo(source.Value); + } + + [Test] + public async Task Generate_ActivityEdit_OutputReRuns(CancellationToken cancellationToken) + { + var edited = ActivityInterface.ReplaceOrdinal("Activity([Tag]", "Activity2([Tag]"); + var inputs = new[] { new IncrementalRunInput([ActivityInterface], []), new IncrementalRunInput([edited], []) }; + var result = await GenerateIncrementalAsync(inputs, Options, cancellationToken); + + var reasons = GetStageReasons(result.Runs[1], "TelemetrySourceGenerator_Activities"); + await Assert + .That(reasons.Any(static reason => reason == IncrementalStepRunReason.Modified)) + .IsTrue() + .Because("editing the interface must re-run generation"); } [Test] public async Task Generate_LoggerEdit_OnlyLoggerOutputChanges(CancellationToken cancellationToken) { - var compilation = CreateCompilation(ActivityInterface, LoggerInterface); - var driver = CreateDriver(); - var first = driver - .RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken) - .GetRunResult(); - var firstSources = GetGeneratedSources(first); - - // Edit only the logger interface. var editedLogger = LoggerInterface.ReplaceOrdinal("void Log(", "void Log2("); - var edited = CreateCompilation(ActivityInterface, editedLogger); - var run = driver.RunGeneratorsAndUpdateCompilation(edited, out _, out _, cancellationToken).GetRunResult(); - var runSources = GetGeneratedSources(run); + var inputs = new[] + { + new IncrementalRunInput([ActivityInterface, LoggerInterface], []), + new IncrementalRunInput([ActivityInterface, editedLogger], []), + }; + var result = await GenerateIncrementalAsync(inputs, Options, cancellationToken); + + var loggerReasons = GetStageReasons(result.Runs[1], "TelemetrySourceGenerator_Logging"); + await Assert + .That(loggerReasons.Any(static reason => reason == IncrementalStepRunReason.Modified)) + .IsTrue() + .Because("editing the logger interface must re-run the logging stage"); + + var activityReasons = GetStageReasons(result.Runs[1], "TelemetrySourceGenerator_Activities"); + await Assert + .That( + activityReasons.All(static reason => + reason is IncrementalStepRunReason.Cached or IncrementalStepRunReason.Unchanged + ) + ) + .IsTrue() + .Because("editing the logger must not invalidate the activity stage"); + + var first = GetGeneratedSources(result.Runs[0].RunResult); + var second = GetGeneratedSources(result.Runs[1].RunResult); - await Assert.That(runSources.Count).IsEqualTo(firstSources.Count); - foreach (var source in firstSources) + foreach (var source in first) { var hintName = source.Key; - var text = source.Value; if ( !hintName.EndsWith(".Activity.g.cs", StringComparison.Ordinal) && !hintName.EndsWith(".Logging.g.cs", StringComparison.Ordinal) ) continue; - var changed = !runSources[hintName].Equals(text, StringComparison.Ordinal); + var changed = !second[hintName].Equals(source.Value, StringComparison.Ordinal); if (hintName.EndsWith(".Activity.g.cs", StringComparison.Ordinal)) await Assert .That(changed) diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.CSharp73.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.CSharp73.cs index 0f2d5e9c..1e9256d9 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.CSharp73.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.CSharp73.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -30,6 +30,19 @@ public interface ITestLogger { var generationResult = await GenerateAsync(source, cancellationToken: cancellationToken); // Assert: GenerateAsync's EnsureValid (default) verifies generated code compiles under C# 7.3. - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with its parameter signature"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExceptionTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExceptionTests.cs index 6d59977f..2d5643e2 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExceptionTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExceptionTests.cs @@ -1,3 +1,4 @@ +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -25,7 +26,18 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "LogEntryWithCustomExceptionType", + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must treat the non-specific exception type as the exception parameter"); } [Test] @@ -51,7 +63,18 @@ public class BadLuckException : Exception { } var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "LogEntryWithCustomExceptionType", + new TypeReference(new TypeIdentity("BadLuckException", "Testing")) + ) + ) + .IsTrue() + .Because("the generated logger must treat the custom exception type as the exception parameter"); } [Test] @@ -81,6 +104,6 @@ public class BadLuckException : Exception { } ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG2000"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExpandEnumerable.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExpandEnumerable.cs index 3002dd3e..b3324950 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExpandEnumerable.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ExpandEnumerable.cs @@ -28,7 +28,12 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "Log")) + .IsTrue() + .Because("the generated logger must contain the log method with the expandable parameter"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.LogProperties.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.LogProperties.cs index 942398d5..ec967872 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.LogProperties.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.LogProperties.cs @@ -1,3 +1,4 @@ +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -34,7 +35,22 @@ public class WeatherForecast var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "LogWeather", + new TypeReference(new TypeIdentity("WeatherForecast", "Testing")) + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with the class-typed parameter"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode("TemperatureC") + .Because("the object's individual properties must be expanded into the log state"); } [Test] @@ -71,7 +87,7 @@ public class WeatherForecast ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG2006"); } [Test] @@ -99,7 +115,18 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod(query, "Log", TypeReference.Create(), TypeReference.Create()) + ) + .IsTrue() + .Because("the generated logger must contain the log method with the exception parameter"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode("Exception = {ex}") + .Because("the message template must reference the passed-in exception"); } [Test] @@ -132,7 +159,22 @@ public class WeatherForecast var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "LogWeatherWithOmit", + new TypeReference(new TypeIdentity("WeatherForecast", "Testing")) + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with the class-typed parameter"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode("TemperatureC") + .Because("the object's individual properties must be expanded into the log state"); } [Test] @@ -165,7 +207,22 @@ public class WeatherForecast var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "LogWeather", + new TypeReference(new TypeIdentity("WeatherForecast", "Testing")) + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with the class-typed parameter"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode("TemperatureC") + .Because("the object's individual properties must be expanded into the log state"); } [Test] @@ -198,7 +255,22 @@ public class WeatherForecast var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "LogWeather", + new TypeReference(new TypeIdentity("WeatherForecast", "Testing")) + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with the class-typed parameter"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode("TemperatureC") + .Because("the object's individual properties must be expanded into the log state"); } [Test] @@ -234,6 +306,25 @@ public class WeatherForecast var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "LogWeather", + new TypeReference(new TypeIdentity("WeatherForecast", "Testing")) + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with the class-typed parameter"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode("TemperatureC") + .Because("the object's individual properties must be expanded into the log state"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .DoesNotContain("IgnoreMe") + .Because("the ignored property must be excluded from the expanded log properties"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.PerMethodV1.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.PerMethodV1.cs index 551a00ce..1c234962 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.PerMethodV1.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.PerMethodV1.cs @@ -1,3 +1,4 @@ +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -27,7 +28,16 @@ public interface ITestLogger { var generationResult = await GenerateAsync(source, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "RegularV2LogEntry", TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the regular V2 log method"); + await Assert + .That(loggerClass.HasMethod(query, "HotPathV1LogEntry", TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the V1-overridden log method"); } [Test] @@ -53,7 +63,16 @@ public interface ITestLogger { var generationResult = await GenerateAsync(source, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "RegularV2LogEntry", TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the regular V2 log method"); + await Assert + .That(loggerClass.HasMethod(query, "HotPathDebugEntry", TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the V1-overridden debug log method"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ScopedGen.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ScopedGen.cs index a869d7b1..ef37942d 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ScopedGen.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.ScopedGen.cs @@ -1,3 +1,4 @@ +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -30,7 +31,16 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "BasicScoped")) + .IsTrue() + .Because("the generated logger must contain the scoped log method"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "BasicScoped", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } [Test] @@ -55,7 +65,24 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "BasicScoped", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the scoped log method with its parameter signature"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "BasicScoped", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } [Test] @@ -82,7 +109,24 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "BasicScoped", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the scoped log method with its parameter signature"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "BasicScoped", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } [Test] @@ -109,7 +153,24 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "BasicScoped", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the scoped log method with its parameter signature"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "BasicScoped", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } [Test] @@ -140,7 +201,7 @@ public interface ITestLogger ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG2007"); } [Test] @@ -171,6 +232,6 @@ public interface ITestLogger ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG2007"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.cs index be96dedd..c185d6ad 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingGen2Tests.cs @@ -29,7 +29,12 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "LogEntryWithGenericTypeParam")) + .IsTrue() + .Because("the generated logger must contain the log method"); } [Test] @@ -60,7 +65,7 @@ public interface ITestLogger<{genericTypeDef}> {{ ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG1004"); } [Test] @@ -91,7 +96,7 @@ public interface ITestLogger<{genericTypeDef}> {{ ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG1004"); } [Test] @@ -113,6 +118,11 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "LogEntryWithMoreThanSixParams")) + .IsTrue() + .Because("the generated logger must contain the log method with more than six parameters"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.BasicGen.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.BasicGen.cs index a69b64c2..14bce164 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.BasicGen.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.BasicGen.cs @@ -1,3 +1,4 @@ +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -26,7 +27,20 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with its parameter signature"); } [Test] @@ -50,7 +64,20 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the implicit log method with its parameter signature"); } [Test] @@ -79,7 +106,21 @@ public interface ITestLogger {{ var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with its parameter signature"); } [Test] @@ -107,7 +148,7 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG2002"); } [Test] @@ -133,7 +174,7 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG2001"); } [Test] @@ -161,7 +202,7 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG2000"); } [Test] @@ -185,7 +226,16 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "Log")) + .IsTrue() + .Because("the generated logger must contain the scoped log method"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "Log", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } [Test] @@ -209,7 +259,24 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the scoped log method with its parameter signature"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "Log", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } [Test] @@ -233,6 +300,15 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "Log", TypeReference.Create(), TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the scoped log method with its parameter signature"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "Log", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.CSharp73.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.CSharp73.cs index b5f6cbf2..545abaf5 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.CSharp73.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.CSharp73.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -28,6 +28,19 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert: GenerateAsync's EnsureValid (default) verifies generated code compiles under C# 7.3. - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with its parameter signature"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.DI.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.DI.cs index 5f54c46c..712d57d4 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.DI.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.DI.cs @@ -1,4 +1,5 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Microsoft.Extensions.DependencyInjection; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -30,7 +31,25 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method"); + var diClass = query.GetClass("TestLoggerCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestLogger", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must register the logger via AddTestLogger"); } [Test] @@ -58,7 +77,25 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method"); + var diClass = query.GetClass("TestLoggerCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestLogger", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must be generated when the interface opts in"); } [Test] @@ -90,7 +127,25 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method"); + var diClass = query.GetClass("TestLoggerCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestLogger", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must be generated when the interface overrides the disabled assembly default"); } [Test] @@ -122,6 +177,23 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method"); + await Assert + .That(query.HasClass("TestLoggerCoreDIExtension", "Microsoft.Extensions.DependencyInjection")) + .IsFalse() + .Because("the DI extension must not be generated when the interface opts out"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.LogAttribute.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.LogAttribute.cs index 6a591535..bda151ab 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.LogAttribute.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.LogAttribute.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -29,7 +29,24 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode(logTargetName) + .Because("the generated log entry must carry the configured name"); } [Test] @@ -64,7 +81,24 @@ public interface ITestLogger { var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method"); + await Assert + .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) + .ContainsGeneratedCode(logTargetName) + .Because("the generated log entry must carry the configured name"); } public static IEnumerable<(string, string)> GetPrefixAndEntryNames() diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.Nested.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.Nested.cs index 7df8cca3..1b3055b7 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.Nested.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.Nested.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -29,7 +29,16 @@ public interface ITestLogger {{ var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", @namespace); + await Assert + .That(loggerClass.HasMethod(query, "Log", TypeReference.Create(), TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the scoped log method"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "Log", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } [Test] @@ -59,7 +68,16 @@ public interface ITestLogger {{ var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", @namespace); + await Assert + .That(loggerClass.HasMethod(query, "Log", TypeReference.Create(), TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the scoped log method"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "Log", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } [Test] @@ -93,6 +111,15 @@ public interface ITestLogger {{ var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", @namespace); + await Assert + .That(loggerClass.HasMethod(query, "Log", TypeReference.Create(), TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the scoped log method"); + await Assert + .That(loggerClass.HasMethodReturnType(query, "Log", TypeReference.Create())) + .IsTrue() + .Because("the scoped log method must return IDisposable"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.NoILoggerRef.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.NoILoggerRef.cs index d38473e4..ed9eae2f 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.NoILoggerRef.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.NoILoggerRef.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -30,7 +32,22 @@ public interface ITestActivities { var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasNoDiagnostics(); + + var query = generationResult.Generated(); + var implClass = query.GetClass("TestActivitiesCore", "Testing"); + await Assert + .That( + implClass.HasMethod( + query, + "Activity", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated implementation must contain the activity method"); } [Test] @@ -63,11 +80,6 @@ public interface ITestActivities { ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - expectsDiagnostics: false, - whenValidatingDiagnosticsIgnoreNonErrors: true, - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasNoErrorDiagnostics(); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.SpecificLogAttribute.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.SpecificLogAttribute.cs index d4bf7e93..e04e4e09 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.SpecificLogAttribute.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.SpecificLogAttribute.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Logging; @@ -29,7 +29,20 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method with its parameter signature"); } [Test] @@ -73,7 +86,80 @@ public interface ITestLogger var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the log method"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log_EventId_1", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the event-id log method"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log_EventId_3", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the positional-event-id log method"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log_MessageTemplate_1", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the first message-template log method"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log_MessageTemplate_2", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the second message-template log method"); + await Assert + .That( + loggerClass.HasMethod( + query, + "Log_MessageTemplate_3", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated logger must contain the positional message-template log method"); } public static IEnumerable SpecificLogAttributeTypes diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.cs index 04fc391d..af7aa907 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorLoggingTests.cs @@ -27,7 +27,12 @@ public interface ITestLogger {{ var generationResult = await GenerateAsync(basicLogger, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("TestLoggerCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "LogEntryWithGenericTypeParam")) + .IsTrue() + .Because("the generated logger must contain the log method"); } [Test] @@ -58,7 +63,7 @@ public interface ITestLogger<{genericTypeDef}> {{ ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG1004"); } [Test] @@ -89,6 +94,6 @@ public interface ITestLogger<{genericTypeDef}> {{ ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG1004"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.CSharp73.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.CSharp73.cs index 61abf60c..cd284e32 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.CSharp73.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.CSharp73.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Metrics; @@ -41,6 +41,32 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetrics, cancellationToken: cancellationToken); // Assert: GenerateAsync's EnsureValid (default) verifies generated code compiles under C# 7.3. - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounter", TypeReference.Create())) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(metricsClass.HasMethod(query, "Counter", TypeReference.Create(), TypeReference.Create())) + .IsTrue() + .Because("the generated metrics class must contain the counter method"); + await Assert + .That( + metricsClass.HasMethod(query, "Histogram", TypeReference.Create(), TypeReference.Create()) + ) + .IsTrue() + .Because("the generated metrics class must contain the histogram method"); + await Assert + .That( + metricsClass.HasMethod( + query, + "UpDownCounter", + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the up-down counter method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Counters.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Counters.cs index d8893d15..0f049b46 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Counters.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Counters.cs @@ -1,3 +1,4 @@ +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator.Metrics; @@ -28,7 +29,12 @@ public interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounter", TypeReference.Create())) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); } [Test] @@ -60,11 +66,6 @@ public interface ITestMetrics // Assert await Assert.That(generationResult).HasDiagnostic("TSG4002"); - //await Assert - // .That(generationResult) - // .HasDiagnostic(DiagnosticLibrary.Metrics.AutoIncrementCountAndMeasurementParam); - - await Assert.That(generationResult).HasDiagnostic("TSG4002"); } [Test] @@ -89,7 +90,14 @@ public interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That( + metricsClass.HasMethod(query, "AutoCounter", TypeReference.Create(), TypeReference.Create()) + ) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); } [Test] @@ -162,7 +170,16 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + string[] counterMethods = ["Counter", "Counter2", "Counter3", "Counter4", "Counter5", "Counter6", "Counter7"]; + foreach (var methodName in counterMethods) + { + await Assert + .That(metricsClass.HasMethod(query, methodName)) + .IsTrue() + .Because($"the generated metrics class must contain the {methodName} method"); + } } [Test] @@ -192,7 +209,16 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + string[] counterMethods = ["Counter1", "Counter2", "Counter3"]; + foreach (var methodName in counterMethods) + { + await Assert + .That(metricsClass.HasMethod(query, methodName)) + .IsTrue() + .Because($"the generated metrics class must contain the {methodName} method"); + } } [Test] @@ -224,6 +250,19 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableCounter")) + .IsTrue() + .Because("the generated metrics class must contain the observable counter method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableCounter2")) + .IsTrue() + .Because("the generated metrics class must contain the second observable counter method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableCounter3")) + .IsTrue() + .Because("the generated metrics class must contain the third observable counter method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.DI.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.DI.cs index 0db1d81e..2818fe9b 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.DI.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.DI.cs @@ -1,4 +1,5 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Microsoft.Extensions.DependencyInjection; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Metrics; @@ -31,7 +32,25 @@ public interface ITestMetrics { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That( + metricsClass.HasMethod( + query, + "Counter", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the counter method"); + var diClass = query.GetClass("TestMetricsCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestMetrics", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must register the metrics via AddTestMetrics"); } [Test] @@ -60,7 +79,25 @@ public interface ITestMetrics { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That( + metricsClass.HasMethod( + query, + "Counter", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the counter method"); + var diClass = query.GetClass("TestMetricsCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestMetrics", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must be generated when the interface opts in"); } [Test] @@ -93,7 +130,25 @@ public interface ITestMetrics { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That( + metricsClass.HasMethod( + query, + "Counter", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the counter method"); + var diClass = query.GetClass("TestMetricsCoreDIExtension", "Microsoft.Extensions.DependencyInjection"); + await Assert + .That(diClass.HasMethod(query, "AddTestMetrics", TypeReference.Create())) + .IsTrue() + .Because("the DI extension must be generated when the interface overrides the disabled assembly default"); } [Test] @@ -126,6 +181,23 @@ public interface ITestMetrics { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That( + metricsClass.HasMethod( + query, + "Counter", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the counter method"); + await Assert + .That(query.HasClass("TestMetricsCoreDIExtension", "Microsoft.Extensions.DependencyInjection")) + .IsFalse() + .Because("the DI extension must not be generated when the interface opts out"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Gauge.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Gauge.cs index 2460db5b..ce90a4c4 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Gauge.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Gauge.cs @@ -1,5 +1,3 @@ -using Purview.Telemetry.SourceGenerator.Infra; - namespace Purview.Telemetry.SourceGenerator.Metrics; partial class TelemetrySourceGeneratorMetricsTests @@ -33,6 +31,19 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableGauge")) + .IsTrue() + .Because("the generated metrics class must contain the observable gauge method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableGauge2")) + .IsTrue() + .Because("the generated metrics class must contain the second observable gauge method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableGauge3")) + .IsTrue() + .Because("the generated metrics class must contain the third observable gauge method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Histogram.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Histogram.cs index fd315930..cbc1e9f2 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Histogram.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.Histogram.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Metrics; @@ -28,6 +28,31 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That( + metricsClass.HasMethod( + query, + "Histogram", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the histogram method"); + await Assert + .That( + metricsClass.HasMethod( + query, + "Histogram1", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the second histogram method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.MeterNaming.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.MeterNaming.cs index 1495fc49..7c4cb884 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.MeterNaming.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.MeterNaming.cs @@ -1,5 +1,3 @@ -using Purview.Telemetry.SourceGenerator.Infra; - namespace Purview.Telemetry.SourceGenerator.Metrics; partial class TelemetrySourceGeneratorMetricsTests @@ -28,7 +26,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("this.is.a.prefix.auto_counter_metric") + .Because("the instrument name must be prefixed and lowercased"); } [Test] @@ -57,7 +64,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("this.is.an.assembly.prefix.test_metrics.auto_counter_metric") + .Because("the instrument name must be prefixed from the assembly and lowercased"); } [Test] @@ -86,7 +102,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("this.is.an.assembly.prefix.this.is.a.prefix.auto_counter_metric") + .Because("the assembly and interface prefixes must be combined"); } [Test] @@ -115,7 +140,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("auto-counter") + .Because("the explicit instrument name must be used"); } [Test] @@ -142,7 +176,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("test_metrics.auto_counter_metric") + .Because("the instrument name must be lowercased"); } [Test] @@ -169,7 +212,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("test_metrics.auto_counter_metric") + .Because("the default instrument name must be lowercased"); } [Test] @@ -196,7 +248,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("AutoCounterMetric") + .Because("the instrument name must preserve its defined case"); } [Test] @@ -223,7 +284,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric")) + .ContainsGeneratedCode("test_metrics.auto_counter_metric") + .Because("the instrument name must be generated for the assembly-derived meter name"); } [Test] @@ -250,7 +320,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("my.custom.meter") + .Because("the assembly meter name must be used"); } [Test] @@ -279,7 +358,16 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("test_metrics.auto_counter_metric") + .Because("the instrument name must be generated for the DotNet-convention meter name"); } [Test] @@ -306,6 +394,15 @@ interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounterMetric")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode("interface.override") + .Because("the interface meter name must override the assembly default"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.NameUnitsDescription.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.NameUnitsDescription.cs index 1a36b70c..35eb7a95 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.NameUnitsDescription.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.NameUnitsDescription.cs @@ -1,5 +1,3 @@ -using Purview.Telemetry.SourceGenerator.Infra; - namespace Purview.Telemetry.SourceGenerator.Metrics; partial class TelemetrySourceGeneratorMetricsTests @@ -32,7 +30,19 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "Metric")) + .IsTrue() + .Because("the generated metrics class must contain the instrument method"); + + // The instrument name is always the first quoted value in the attribute application. + var instrumentName = attribute.Split('"')[1]; + await Assert + .That(generationResult.GetSource("TestMetricsCore.Metric.g.cs")) + .ContainsGeneratedCode(instrumentName) + .Because("the instrument must carry the configured name"); } public static IEnumerable<(string, string)> NameUnitsDescriptorData diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.ObservableSpecific.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.ObservableSpecific.cs index b09efbac..c7932b8b 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.ObservableSpecific.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.ObservableSpecific.cs @@ -1,5 +1,3 @@ -using Purview.Telemetry.SourceGenerator.Infra; - namespace Purview.Telemetry.SourceGenerator.Metrics; partial class TelemetrySourceGeneratorMetricsTests @@ -32,7 +30,20 @@ public interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "Counter")) + .IsTrue() + .Because("the generated metrics class must contain the observable counter method"); + await Assert + .That(metricsClass.HasMethod(query, "Gauge")) + .IsTrue() + .Because("the generated metrics class must contain the observable gauge method"); + await Assert + .That(metricsClass.HasMethod(query, "UpDown")) + .IsTrue() + .Because("the generated metrics class must contain the observable up-down counter method"); } [Test] @@ -64,6 +75,19 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "Counter")) + .IsTrue() + .Because("the generated metrics class must contain the observable counter method"); + await Assert + .That(metricsClass.HasMethod(query, "Gauge")) + .IsTrue() + .Because("the generated metrics class must contain the observable gauge method"); + await Assert + .That(metricsClass.HasMethod(query, "UpDown")) + .IsTrue() + .Because("the generated metrics class must contain the observable up-down counter method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.TagListCount.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.TagListCount.cs index 54425fda..42ad3dbf 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.TagListCount.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.TagListCount.cs @@ -1,5 +1,3 @@ -using Purview.Telemetry.SourceGenerator.Infra; - namespace Purview.Telemetry.SourceGenerator.Metrics; partial class TelemetrySourceGeneratorMetricsTests @@ -45,6 +43,24 @@ public interface ITestMetrics var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + string[] autoCounters = + [ + "NoTagList0", + "NoTagList1", + "NoTagList2", + "NoTagList3", + "HasTagList4", + "HasTagList5", + "HasTagList10", + ]; + foreach (var methodName in autoCounters) + { + await Assert + .That(metricsClass.HasMethod(query, methodName)) + .IsTrue() + .Because($"the generated metrics class must contain the {methodName} method"); + } } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.UpDown.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.UpDown.cs index 0607a09b..31897663 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.UpDown.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.UpDown.cs @@ -1,4 +1,4 @@ -using Purview.Telemetry.SourceGenerator.Infra; +using Purview.SourceGeneratorFramework; namespace Purview.Telemetry.SourceGenerator.Metrics; @@ -28,7 +28,32 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That( + metricsClass.HasMethod( + query, + "UpDown", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the up-down counter method"); + await Assert + .That( + metricsClass.HasMethod( + query, + "UpDown2", + TypeReference.Create(), + TypeReference.Create(), + TypeReference.Create() + ) + ) + .IsTrue() + .Because("the generated metrics class must contain the second up-down counter method"); } [Test] @@ -60,6 +85,19 @@ public interface ITestMetrics { var generationResult = await GenerateAsync(basicMetric, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableUpDown")) + .IsTrue() + .Because("the generated metrics class must contain the observable up-down counter method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableUpDown2")) + .IsTrue() + .Because("the generated metrics class must contain the second observable up-down counter method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableUpDown3")) + .IsTrue() + .Because("the generated metrics class must contain the third observable up-down counter method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs index 4537249c..7ee2541a 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorMetricsTests.cs @@ -52,7 +52,40 @@ public interface ITestMetrics var generationResult = await GenerateAsync(basicActivity, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var metricsClass = query.GetClass("TestMetricsCore", "Testing"); + await Assert + .That(metricsClass.HasMethod(query, "AutoCounter")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(metricsClass.HasMethod(query, "Counter_AutoIncrement")) + .IsTrue() + .Because("the generated metrics class must contain the auto-increment counter method"); + await Assert + .That(metricsClass.HasMethod(query, "Counter")) + .IsTrue() + .Because("the generated metrics class must contain the counter method"); + await Assert + .That(metricsClass.HasMethod(query, "Histogram")) + .IsTrue() + .Because("the generated metrics class must contain the histogram method"); + await Assert + .That(metricsClass.HasMethod(query, "UpDownCounter")) + .IsTrue() + .Because("the generated metrics class must contain the up-down counter method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableCounter")) + .IsTrue() + .Because("the generated metrics class must contain the observable counter method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableGauge")) + .IsTrue() + .Because("the generated metrics class must contain the observable gauge method"); + await Assert + .That(metricsClass.HasMethod(query, "ObservableUpDownCounter")) + .IsTrue() + .Because("the generated metrics class must contain the observable up-down counter method"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.Docs_README.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.Docs_README.cs index f3d9bd4b..6e57fa59 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.Docs_README.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.Docs_README.cs @@ -1,5 +1,3 @@ -using Purview.Telemetry.SourceGenerator.Infra; - namespace Purview.Telemetry.SourceGenerator; partial class TelemetrySourceGeneratorTests @@ -76,7 +74,39 @@ interface IEntityStoreTelemetry ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("GettingEntityFromStore")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("GetDuration")) + .IsTrue() + .Because("the generated class must contain the event method"); + await Assert + .That(query.HasMethod("RetrievedEntity")) + .IsTrue() + .Because("the generated class must contain the context method"); + await Assert + .That(query.HasMethod("AScopedLogEntry")) + .IsTrue() + .Because("the generated class must contain the scoped log method"); + await Assert + .That(query.HasMethod("LogMessage")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("ExplicitInfoMessage")) + .IsTrue() + .Because("the generated class must contain the info log method"); + await Assert + .That(query.HasMethod("ExplicitErrorMessage")) + .IsTrue() + .Because("the generated class must contain the error log method"); + await Assert + .That(query.HasMethod("RetrievingEntity")) + .IsTrue() + .Because("the generated class must contain the auto-counter method"); } [Test] @@ -160,7 +190,23 @@ enum ItemTypes ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("ProcessingWorkItem")) + .IsTrue() + .Because("the generated logger must contain the scoped log method"); + await Assert + .That(query.HasMethod("ProcessingItemType")) + .IsTrue() + .Because("the generated logger must contain the item-type log method"); + await Assert + .That(query.HasMethod("FailedToProcessWorkItem")) + .IsTrue() + .Because("the generated logger must contain the error log method"); + await Assert + .That(query.HasMethod("ProcessingComplete")) + .IsTrue() + .Because("the generated logger must contain the completion log method"); } [Test] @@ -209,7 +255,39 @@ interface IMeterTelemetry ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("AutoCounterMeter")) + .IsTrue() + .Because("the generated metrics class must contain the auto-counter method"); + await Assert + .That(query.HasMethod("AutoIncrementMeter")) + .IsTrue() + .Because("the generated metrics class must contain the auto-increment counter method"); + await Assert + .That(query.HasMethod("CounterMeter")) + .IsTrue() + .Because("the generated metrics class must contain the counter method"); + await Assert + .That(query.HasMethod("HistogramMeter")) + .IsTrue() + .Because("the generated metrics class must contain the histogram method"); + await Assert + .That(query.HasMethod("ObservableCounterMeter")) + .IsTrue() + .Because("the generated metrics class must contain the observable counter method"); + await Assert + .That(query.HasMethod("ObservableGaugeMeter")) + .IsTrue() + .Because("the generated metrics class must contain the observable gauge method"); + await Assert + .That(query.HasMethod("ObservableUpDownCounter")) + .IsTrue() + .Because("the generated metrics class must contain the observable up-down counter method"); + await Assert + .That(query.HasMethod("UpDownCounterMeter")) + .IsTrue() + .Because("the generated metrics class must contain the up-down counter method"); } [Test] @@ -251,6 +329,26 @@ interface IServiceTelemetry ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartAnActivity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("AnInterestingEvent")) + .IsTrue() + .Because("the generated class must contain the event method"); + await Assert + .That(query.HasMethod("InterestingInfo")) + .IsTrue() + .Because("the generated class must contain the context method"); + await Assert + .That(query.HasMethod("ProcessingEntity")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("AnAutoIncrement")) + .IsTrue() + .Because("the generated class must contain the auto-increment counter method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.IRL.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.IRL.cs index 8dd3069b..e9416c32 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.IRL.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.IRL.cs @@ -1,5 +1,3 @@ -using Purview.Telemetry.SourceGenerator.Infra; - namespace Purview.Telemetry.SourceGenerator; partial class TelemetrySourceGeneratorTests @@ -92,10 +90,24 @@ public interface ICacheServiceProviderTelemetry ); // Assert - await TestHelpers.VerifyAsync( - generationResult, - whenValidatingDiagnosticsIgnoreNonErrors: true, - cancellationToken: cancellationToken - ); + await Assert.That(generationResult).HasNoErrorDiagnostics(); + + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("GetFromCache")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("SerializePayload")) + .IsTrue() + .Because("the generated class must contain the serialize-payload activity method"); + await Assert + .That(query.HasMethod("FailedToGetFromCache")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("CacheHit")) + .IsTrue() + .Because("the generated class must contain the cache-hit event method"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.InvalidReturnTypes.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.InvalidReturnTypes.cs index 472cb8fd..5a68727f 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.InvalidReturnTypes.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.InvalidReturnTypes.cs @@ -1,3 +1,4 @@ +using Purview.SourceGeneratorFramework; using Purview.Telemetry.SourceGenerator.Infra; namespace Purview.Telemetry.SourceGenerator; @@ -268,7 +269,12 @@ public interface IInvalidTelemetry var generationResult = await GenerateAsync(code, cancellationToken: cancellationToken); // Assert - this should succeed since void-returning logs are valid non-scoped logs - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + var loggerClass = query.GetClass("InvalidTelemetryCore", "Testing"); + await Assert + .That(loggerClass.HasMethod(query, "ValidNonScopedLog", TypeReference.Create())) + .IsTrue() + .Because("the generated logger must contain the valid non-scoped log method"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.MultiGeneration.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.MultiGeneration.cs index 242ccb34..4231bf10 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.MultiGeneration.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.MultiGeneration.cs @@ -34,7 +34,15 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartActivity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("LogOperation")) + .IsTrue() + .Because("the generated class must contain the log method"); } [Test] @@ -63,7 +71,15 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartActivity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("IncrementCounter")) + .IsTrue() + .Because("the generated class must contain the counter method"); } [Test] @@ -92,7 +108,15 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("LogOperation")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("IncrementCounter")) + .IsTrue() + .Because("the generated class must contain the counter method"); } [Test] @@ -125,7 +149,19 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartActivity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("LogOperation")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("IncrementCounter")) + .IsTrue() + .Because("the generated class must contain the counter method"); } [Test] @@ -154,7 +190,11 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - should generate both activity and logging for this method - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("TraceAndLogMethod")) + .IsTrue() + .Because("the generated class must contain the multi-target method"); } [Test] @@ -183,7 +223,11 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - should generate both activity and counter for this method - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("TraceAndCountMethod")) + .IsTrue() + .Because("the generated class must contain the multi-target method"); } [Test] @@ -212,7 +256,11 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - should generate both logging and counter for this method - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("LogAndCountMethod")) + .IsTrue() + .Because("the generated class must contain the multi-target method"); } [Test] @@ -243,7 +291,11 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - should generate activity, logging, and counter for this method - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("FullTelemetryMethod")) + .IsTrue() + .Because("the generated class must contain the multi-target method"); } [Test] @@ -336,7 +388,39 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartActivity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("RecordEvent")) + .IsTrue() + .Because("the generated class must contain the event method"); + await Assert + .That(query.HasMethod("TraceMessage")) + .IsTrue() + .Because("the generated class must contain the trace log method"); + await Assert + .That(query.HasMethod("DebugMessage")) + .IsTrue() + .Because("the generated class must contain the debug log method"); + await Assert + .That(query.HasMethod("InfoMessage")) + .IsTrue() + .Because("the generated class must contain the info log method"); + await Assert + .That(query.HasMethod("WarnMessage")) + .IsTrue() + .Because("the generated class must contain the warning log method"); + await Assert + .That(query.HasMethod("ErrorMessage")) + .IsTrue() + .Because("the generated class must contain the error log method"); + await Assert + .That(query.HasMethod("CriticalMessage")) + .IsTrue() + .Because("the generated class must contain the critical log method"); } [Test] @@ -385,7 +469,39 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartActivity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("IncrementCounter")) + .IsTrue() + .Because("the generated class must contain the counter method"); + await Assert + .That(query.HasMethod("AutoIncrement")) + .IsTrue() + .Because("the generated class must contain the auto-counter method"); + await Assert + .That(query.HasMethod("UpdateUpDownCounter")) + .IsTrue() + .Because("the generated class must contain the up-down counter method"); + await Assert + .That(query.HasMethod("RecordHistogram")) + .IsTrue() + .Because("the generated class must contain the histogram method"); + await Assert + .That(query.HasMethod("GetObservableCounter")) + .IsTrue() + .Because("the generated class must contain the observable counter method"); + await Assert + .That(query.HasMethod("GetObservableGauge")) + .IsTrue() + .Because("the generated class must contain the observable gauge method"); + await Assert + .That(query.HasMethod("GetObservableUpDownCounter")) + .IsTrue() + .Because("the generated class must contain the observable up-down counter method"); } [Test] @@ -425,7 +541,27 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("LogOperation")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("IncrementOperationCounter")) + .IsTrue() + .Because("the generated class must contain the counter method"); + await Assert + .That(query.HasMethod("RecordOperationDuration")) + .IsTrue() + .Because("the generated class must contain the histogram method"); + await Assert + .That(query.HasMethod("InfoLog")) + .IsTrue() + .Because("the generated class must contain the info log method"); + await Assert + .That(query.HasMethod("UpdateActiveConnections")) + .IsTrue() + .Because("the generated class must contain the up-down counter method"); } [Test] @@ -473,7 +609,23 @@ void LogProcessing( var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("ProcessRequest")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("LogProcessing")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("IncrementRequestCount")) + .IsTrue() + .Because("the generated class must contain the auto-counter method"); + await Assert + .That(query.HasMethod("RecordRequestDuration")) + .IsTrue() + .Because("the generated class must contain the histogram method"); } [Test] @@ -510,7 +662,23 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartOperation")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("SetContext")) + .IsTrue() + .Because("the generated class must contain the context method"); + await Assert + .That(query.HasMethod("RecordEvent")) + .IsTrue() + .Because("the generated class must contain the event method"); + await Assert + .That(query.HasMethod("CountOperation")) + .IsTrue() + .Because("the generated class must contain the counter method"); } [Test] @@ -550,7 +718,19 @@ public void ExcludedMethod(string message) var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartActivity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("LogOperation")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("ExcludedMethod")) + .IsFalse() + .Because("the excluded method must not be generated in the telemetry implementation"); } [Test] @@ -621,7 +801,19 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("StartActivity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("LogOperation")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("IncrementCounter")) + .IsTrue() + .Because("the generated class must contain the counter method"); } [Test] @@ -690,7 +882,15 @@ public interface IMultiTelemetry var generationResult = await GenerateAsync(multiGen, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("LogOperation")) + .IsTrue() + .Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("InfoMessage")) + .IsTrue() + .Because("the generated class must contain the info log method"); } [Test] diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryBasic.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryBasic.cs index abfa4f0a..9b7216ec 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryBasic.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryBasic.cs @@ -67,7 +67,28 @@ partial interface ITestTelemetry ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("Activity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("Event")) + .IsTrue() + .Because("the generated class must contain the event method"); + await Assert + .That(query.HasMethod("Context")) + .IsTrue() + .Because("the generated class must contain the context method"); + await Assert.That(query.HasMethod("Log")).IsTrue().Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("LogScope")) + .IsTrue() + .Because("the generated class must contain the scoped log method"); + await Assert + .That(query.HasMethod("Counter")) + .IsTrue() + .Because("the generated class must contain the counter method"); } [Test] @@ -107,7 +128,28 @@ public interface ITestTelemetry var generationResult = await GenerateAsync(basicTelemetry, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("Activity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("Event")) + .IsTrue() + .Because("the generated class must contain the event method"); + await Assert + .That(query.HasMethod("Context")) + .IsTrue() + .Because("the generated class must contain the context method"); + await Assert.That(query.HasMethod("Log")).IsTrue().Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("LogScope")) + .IsTrue() + .Because("the generated class must contain the scoped log method"); + await Assert + .That(query.HasMethod("Counter")) + .IsTrue() + .Because("the generated class must contain the counter method"); } [Test] @@ -149,7 +191,28 @@ public interface ITestTelemetry var generationResult = await GenerateAsync(basicTelemetry, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("Activity")) + .IsTrue() + .Because("the generated class must contain the activity method"); + await Assert + .That(query.HasMethod("Event")) + .IsTrue() + .Because("the generated class must contain the event method"); + await Assert + .That(query.HasMethod("Context")) + .IsTrue() + .Because("the generated class must contain the context method"); + await Assert.That(query.HasMethod("Log")).IsTrue().Because("the generated class must contain the log method"); + await Assert + .That(query.HasMethod("LogScope")) + .IsTrue() + .Because("the generated class must contain the scoped log method"); + await Assert + .That(query.HasMethod("Counter")) + .IsTrue() + .Because("the generated class must contain the counter method"); } [Test] @@ -267,7 +330,11 @@ public interface ITestTelemetry var generationResult = await GenerateAsync(basicTelemetry, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("EventMethod")) + .IsTrue() + .Because("the generated class must contain the named-exception event method"); } [Test] @@ -297,7 +364,11 @@ public interface ITestTelemetry var generationResult = await GenerateAsync(basicTelemetry, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasMethod("EventMethod")) + .IsTrue() + .Because("the generated class must contain the named-exception event method"); } [Test] @@ -350,6 +421,6 @@ public interface ITestTelemetry ); // Assert - await TestHelpers.VerifyAsync(generationResult, expectsDiagnostics: true, cancellationToken: cancellationToken); + await Assert.That(generationResult).HasDiagnostic("TSG1002"); } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryNamesNamespace.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryNamesNamespace.cs index bf4315b9..76b94147 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryNamesNamespace.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.TelemetryNamesNamespace.cs @@ -1,5 +1,3 @@ -using Purview.Telemetry.SourceGenerator.Infra; - namespace Purview.Telemetry.SourceGenerator; partial class TelemetrySourceGeneratorTests @@ -43,8 +41,6 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); - await Assert .That(generationResult.GetSource("TestActivitiesCore.Activity.g.cs")) .ContainsGeneratedCode("namespace Custom.Telemetry"); @@ -91,8 +87,6 @@ public interface ITestLogger { ); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); - await Assert .That(generationResult.GetSource("TestLoggerCore.Logging.g.cs")) .ContainsGeneratedCode("namespace Testing"); diff --git a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.cs b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.cs index f23bb506..92b21ea7 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Generators/TelemetrySourceGeneratorTests.cs @@ -20,7 +20,23 @@ namespace Testing; var generationResult = await GenerateAsync(empty, cancellationToken: cancellationToken); // Assert - await TestHelpers.VerifyAsync(generationResult, cancellationToken: cancellationToken); + var query = generationResult.Generated(); + await Assert + .That(query.HasClass("ActivitySourceAttribute", "Purview.Telemetry")) + .IsTrue() + .Because("the shared activity-source attribute must be generated"); + await Assert + .That(query.HasClass("LoggerAttribute", "Purview.Telemetry")) + .IsTrue() + .Because("the shared logger attribute must be generated"); + await Assert + .That(query.HasClass("MeterAttribute", "Purview.Telemetry")) + .IsTrue() + .Because("the shared meter attribute must be generated"); + await Assert + .That(query.HasClass("TelemetryGenerationAttribute", "Purview.Telemetry")) + .IsTrue() + .Because("the shared telemetry-generation attribute must be generated"); } public static IEnumerable BasicGenericParameters diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetryAnalyzerTestOptions.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetryAnalyzerTestOptions.cs new file mode 100644 index 00000000..edcc1542 --- /dev/null +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetryAnalyzerTestOptions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using System.Diagnostics.Metrics; +using Microsoft.Extensions.Logging; + +namespace Purview.Telemetry.SourceGenerator.Infra; + +/// +/// Options for standalone analyzer tests. The analyzer resolves the telemetry attribute types by +/// metadata name, but a standalone analyzer run does not execute the generator's +/// RegisterPostInitializationOutput, so the attribute definitions are supplied via +/// . Seeded assemblies mirror +/// ; metrics types are only referenced when +/// available (.NET Core+). +/// +public sealed record TelemetryAnalyzerTestOptions : AnalyzerTestOptions +{ + public TelemetryAnalyzerTestOptions() + { + AdditionalNamespaces = ["Purview.Telemetry"]; + + AdditionalAssemblyTypes = [typeof(ILogger), typeof(ActivitySource)]; + + // System.Diagnostics.Metrics is not available on .NET Framework. + if ( + typeof(ActivitySource).Assembly.GetType("System.Diagnostics.Metrics.Meter", throwOnError: false) is not null + ) + AdditionalAssemblyTypes = AdditionalAssemblyTypes.Add(typeof(Meter)); + + AdditionalSources = + [ + "[assembly: Purview.Telemetry.TelemetryGeneration(GenerateDependencyExtension = false)]", + TelemetryTestAttributeSource.Attributes, + ]; + } +} diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetrySourceGeneratorTestOptions.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetrySourceGeneratorTestOptions.cs index 0adb5d03..cd56f770 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetrySourceGeneratorTestOptions.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetrySourceGeneratorTestOptions.cs @@ -25,6 +25,9 @@ public TelemetrySourceGeneratorTestOptions() AnalyzerTypes = [typeof(TelemetryDiagnosticAnalyzer)]; + // Undisposed CodeWriter scopes fail tests, so emitters must dispose every scope they open. + ValidateCodeWriterScopes = true; + // Most tests do not want a dependency-injection extension generated. AdditionalSources = ["[assembly: Purview.Telemetry.TelemetryGeneration(GenerateDependencyExtension = false)]"]; diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetryTestAttributeSource.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetryTestAttributeSource.cs new file mode 100644 index 00000000..eba46bda --- /dev/null +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/TelemetryTestAttributeSource.cs @@ -0,0 +1,71 @@ +namespace Purview.Telemetry.SourceGenerator.Infra; + +/// +/// Minimal definitions of the telemetry attribute types, mirroring the shapes the analyzer's shared +/// rules resolve. A standalone analyzer run does not execute the generator, so these are compiled +/// into the analyzer test compilation via . +/// +static class TelemetryTestAttributeSource +{ + public const string Attributes = """ + + namespace Purview.Telemetry; + + [System.AttributeUsage(System.AttributeTargets.Interface | System.AttributeTargets.Assembly, Inherited = false)] + public sealed class TelemetryGenerationAttribute : System.Attribute + { + public bool GenerateDependencyExtension { get; set; } + + public TelemetryGenerationAttribute(bool generateDependencyExtension = true) => + GenerateDependencyExtension = generateDependencyExtension; + } + + [System.AttributeUsage(System.AttributeTargets.Interface, Inherited = false)] + public sealed class ActivitySourceAttribute : System.Attribute + { + public string? Name { get; set; } + + public ActivitySourceAttribute() { } + + public ActivitySourceAttribute(string? name) => Name = name; + } + + [System.AttributeUsage(System.AttributeTargets.Method, Inherited = false)] + public sealed class ActivityAttribute : System.Attribute { } + + [System.AttributeUsage(System.AttributeTargets.Method, Inherited = false)] + public sealed class EventAttribute : System.Attribute { } + + [System.AttributeUsage(System.AttributeTargets.Method, Inherited = false)] + public sealed class ContextAttribute : System.Attribute { } + + [System.AttributeUsage(System.AttributeTargets.Parameter, Inherited = false)] + public sealed class TagAttribute : System.Attribute { } + + [System.AttributeUsage(System.AttributeTargets.Parameter, Inherited = false)] + public sealed class BaggageAttribute : System.Attribute { } + + [System.AttributeUsage(System.AttributeTargets.Interface, Inherited = false)] + public sealed class LoggerAttribute : System.Attribute { } + + [System.AttributeUsage(System.AttributeTargets.Method, Inherited = false)] + public sealed class LogAttribute : System.Attribute { } + + [System.AttributeUsage(System.AttributeTargets.Interface, Inherited = false)] + public sealed class MeterAttribute : System.Attribute + { + public string? Name { get; set; } + + public MeterAttribute() { } + + public MeterAttribute(string? name) => Name = name; + } + + [System.AttributeUsage(System.AttributeTargets.Method, Inherited = false)] + public sealed class CounterAttribute : System.Attribute { } + + [System.AttributeUsage(System.AttributeTargets.Parameter, Inherited = false)] + public sealed class InstrumentMeasurementAttribute : System.Attribute { } + + """; +} diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs index 34b8ad02..5f8801a7 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs @@ -40,32 +40,4 @@ public static List GetCasePermutations(string input) return result; } - - public static async Task VerifyAsync( - DriverRunResult result, - bool expectsDiagnostics = false, - bool whenValidatingDiagnosticsIgnoreNonErrors = false, - CancellationToken cancellationToken = default - ) - { - cancellationToken.ThrowIfCancellationRequested(); - - var diag = result.DriverResult.Diagnostics.AddRange(result.AnalyzerResult?.Diagnostics ?? []).ToArray(); - if (whenValidatingDiagnosticsIgnoreNonErrors) - diag = [.. diag.Where(m => m.Severity == DiagnosticSeverity.Error)]; - - if (expectsDiagnostics) - { - await Assert.That(diag).IsNotEmpty(); - } - else - { - await Assert - .That(diag) - .IsEmpty() - .Because( - $"Expected no diagnostics, but found: [{string.Join(", ", diag.Select(d => d.Id).Distinct())}]" - ); - } - } } diff --git a/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs b/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs index bd2017bc..ddcff189 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Refactorings/CodeRefactoringTestBase.cs @@ -9,6 +9,15 @@ namespace Purview.Telemetry.SourceGenerator.Refactorings; /// Base class for testing implementations. /// The source code should include a $$ marker to indicate the cursor position. /// +/// +/// This deliberately does not use the framework's TUnitRefactoringTestBase/RefactorAsync: +/// every refactoring provider here registers a top-level group action (nested +/// Class/Document/Project/Solution scopes) and several tests assert that no action is offered. +/// RefactoringTestRunner selects only from the flat CodeActions list (by +/// CodeActionIndex/EquivalenceKey) and throws when no action resolves, so it cannot +/// apply a nested action or represent an empty action set. See the framework's +/// RefactoringTestRunner.RunAsync for the selection logic. +/// public abstract class CodeRefactoringTestBase { protected static async Task ApplyRefactoringAsync( diff --git a/src/tests/SourceGenerator.IntegrationTests/TelemetrySourceGeneratorCachingTests.cs b/src/tests/SourceGenerator.IntegrationTests/TelemetrySourceGeneratorCachingTests.cs deleted file mode 100644 index 5b0431fc..00000000 --- a/src/tests/SourceGenerator.IntegrationTests/TelemetrySourceGeneratorCachingTests.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System.Diagnostics; -using System.Diagnostics.Metrics; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Purview.Telemetry.SourceGenerator.Infra; - -namespace Purview.Telemetry.SourceGenerator; - -/// -/// Verifies the incremental pipeline caches across unchanged runs and invalidates only -/// the affected target when an interface is edited. -/// -public class TelemetrySourceGeneratorCachingTests -{ - const string ActivityInterface = """ - using Purview.Telemetry; - - namespace Testing; - - [ActivitySource("testing-activity-source")] - public interface ITestActivities - { - [Activity] - System.Diagnostics.Activity? Activity([Tag]string stringParam); - } - """; - - const string LoggerInterface = """ - using Microsoft.Extensions.Logging; - using Purview.Telemetry; - - namespace Testing; - - [Logger] - public interface ITestLogger - { - void Log([Tag]string stringParam); - } - """; - - const string UnrelatedType = """ - namespace Testing; - - public class Unrelated { public int Value { get; set; } } - """; - - static ImmutableArray References { get; } = BuildReferences(); - - static ImmutableArray BuildReferences() - { - var trusted = ((string?)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES") ?? "") - .Split([Path.PathSeparator], StringSplitOptions.RemoveEmptyEntries) - .Select(path => MetadataReference.CreateFromFile(path)) - .ToList(); - - // .NET Framework does not populate TRUSTED_PLATFORM_ASSEMBLIES. - if (trusted.Count == 0) - { - trusted.AddRange( - AppDomain - .CurrentDomain.GetAssemblies() - .Where(static assembly => !assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location)) - .Select(static assembly => MetadataReference.CreateFromFile(assembly.Location)) - ); - } - - foreach ( - var type in new[] - { - typeof(Activity), - typeof(Meter), - typeof(IServiceCollection), - typeof(LogLevel), - typeof(LogPropertiesAttribute), - } - ) - { - trusted.Add(MetadataReference.CreateFromFile(type.Assembly.Location)); - } - - return [.. trusted]; - } - - static CSharpCompilation CreateCompilation(params string[] sources) - { - var trees = sources.Select(source => CSharpSyntaxTree.ParseText(source)); - return CSharpCompilation.Create( - "CachingTest", - trees, - References, - new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - ); - } - - static CSharpGeneratorDriver CreateDriver() => - CSharpGeneratorDriver.Create( - generators: [new TelemetrySourceGenerator().AsSourceGenerator()], - driverOptions: new GeneratorDriverOptions( - IncrementalGeneratorOutputKind.None, - trackIncrementalGeneratorSteps: true - ) - ); - - static ImmutableArray GetSourceOutputReasons(GeneratorDriverRunResult result) - { - return - [ - .. result - .Results.SelectMany(static r => r.TrackedSteps) - .Where(static kvp => kvp.Key == "SourceOutput") - .SelectMany(static kvp => kvp.Value) - .SelectMany(static runStep => runStep.Outputs) - .Select(static o => o.Reason), - ]; - } - - [Test] - public async Task Generate_UnchangedCompilation_OutputCached(CancellationToken cancellationToken) - { - var compilation = CreateCompilation(ActivityInterface); - GeneratorDriver driver = CreateDriver(); - - driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken); - var first = driver.GetRunResult(); - await Assert - .That(GetSourceOutputReasons(first).All(static r => r == IncrementalStepRunReason.New)) - .IsTrue() - .Because("the first run must produce every output"); - - // Same compilation, same driver: every output must be cached/unchanged. - driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken); - var second = driver.GetRunResult(); - var reasons = GetSourceOutputReasons(second); - await Assert.That(reasons).IsNotEmpty(); - await Assert - .That( - reasons.All(static reason => - reason is IncrementalStepRunReason.Cached or IncrementalStepRunReason.Unchanged - ) - ) - .IsTrue() - .Because("an unchanged compilation must not re-run any target"); - } - - [Test] - public async Task Generate_UnrelatedChange_OutputStaysCached(CancellationToken cancellationToken) - { - var compilation = CreateCompilation(ActivityInterface, LoggerInterface); - GeneratorDriver driver = CreateDriver(); - driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken); - - // Adding an unrelated type must not invalidate any output. - var changed = CreateCompilation(ActivityInterface, LoggerInterface, UnrelatedType); - driver = driver.RunGeneratorsAndUpdateCompilation(changed, out _, out _, cancellationToken); - var reasons = GetSourceOutputReasons(driver.GetRunResult()); - await Assert.That(reasons).IsNotEmpty(); - await Assert - .That( - reasons.All(static reason => - reason is IncrementalStepRunReason.Cached or IncrementalStepRunReason.Unchanged - ) - ) - .IsTrue() - .Because("an unrelated edit must not invalidate the generated output"); - } - - [Test] - public async Task Generate_ActivityEdit_OutputReRuns(CancellationToken cancellationToken) - { - var compilation = CreateCompilation(ActivityInterface); - GeneratorDriver driver = CreateDriver(); - driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _, cancellationToken); - - // Edit the activity interface source (change the method name so the output changes). - var edited = CreateCompilation(ActivityInterface.ReplaceOrdinal("Activity([Tag]", "Activity2([Tag]")); - driver = driver.RunGeneratorsAndUpdateCompilation(edited, out _, out _, cancellationToken); - var reasons = GetSourceOutputReasons(driver.GetRunResult()); - await Assert - .That(reasons.Any(static reason => reason == IncrementalStepRunReason.Modified)) - .IsTrue() - .Because("editing the interface must re-run generation"); - } -}