diff --git a/.agents/prompts/refactor-source-generator-to-codewriter.prompt.md b/.agents/prompts/refactor-source-generator-to-codewriter.prompt.md index d1bbb1f2..e230d224 100644 --- a/.agents/prompts/refactor-source-generator-to-codewriter.prompt.md +++ b/.agents/prompts/refactor-source-generator-to-codewriter.prompt.md @@ -19,11 +19,11 @@ Refactor the selected legacy emitter implementation from manual `string` / `Stri ### Requirements 1. Use structured declaration APIs where applicable: - - `WriteClass/WriteStruct/WriteRecordClass/WriteInterface/WriteEnum` - - `WriteMethod`, `WriteProperty`, `WriteField`, `WriteConstructor` + - `Class/Struct/RecordClass/Interface/Enum` + - `Method`, `Property`, `Field`, `Constructor` 2. Use XML helper extensions instead of raw `///` composition: - `XmlSummary`, `XmlParam`, `XmlReturn`, `XmlRemarks`, `XmlCode` or `XmlCodeBlock` -3. Use `TypeReferenceOptions` when type text becomes complex (nullability, generics, arrays). +3. Use `TypeReference` when type text becomes complex (nullability, generics, arrays). 4. Ensure writer lifetime is output-scoped (`generationContext.CreateCodeWriter()` inside callback). 5. Preserve behavior, diagnostics, and generated names. 6. Keep changes minimal and focused; do not reformat unrelated logic. diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e1c5ba67..c96c9270 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -11,21 +11,10 @@ concurrency: jobs: build: name: Build and test - runs-on: ubuntu-latest - timeout-minutes: 30 - 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: Run PR pipeline - run: dotnet run --project build/PipelineCLI/PipelineCLI.csproj --configuration Release + uses: purview-dev/build/.github/workflows/purview-build.yml@main + with: + build-version: "0.2.1" + secrets: inherit samples: name: Build and test samples @@ -42,13 +31,8 @@ jobs: with: dotnet-version: "10.0.x" - # This is so the analyzer projects can be used in the sample app projects. - # The analyzer projects are not referenced by the sample app projects, the assemblies are, so they need to be built first. - - name: Run pipeline build - run: dotnet run --project build/PipelineCLI/PipelineCLI.csproj --configuration Release -- --Build:RunTests=false --Release:Mode=None - - 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 + run: dotnet test samples/SampleApp/SampleApp.slnx --configuration Release --no-build \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1396ecfe..e48c62c0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,48 +6,14 @@ on: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + cancel-in-progress: false jobs: release: name: Release packages - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: write - 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: Check for version bump - id: version - shell: bash - run: | - VERSION=$(node -p "require('./package.json').version") - TAG="v$VERSION" - if git rev-parse "$TAG" >/dev/null 2>&1; then - echo "Version $VERSION is already tagged as $TAG. Skipping release." - echo "should_publish=false" >> "$GITHUB_OUTPUT" - else - echo "New version $VERSION detected. Releasing $TAG." - echo "should_publish=true" >> "$GITHUB_OUTPUT" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "tag=$TAG" >> "$GITHUB_OUTPUT" - fi - - - name: Run release pipeline - if: steps.version.outputs.should_publish == 'true' - env: - Release__ShouldPublish: true - Release__Mode: NuGet - Build__RunPack: true - NuGet__ApiKey: ${{ secrets.NUGET__APIKEY }} - GITHUB_TOKEN: ${{ github.token }} - run: dotnet run --project build/PipelineCLI/PipelineCLI.csproj --configuration Release + uses: purview-dev/build/.github/workflows/purview-release.yml@main + with: + build-version: "0.2.1" + release-mode: NuGet + release-branch: main + secrets: inherit \ No newline at end of file diff --git a/.gitignore b/.gitignore index a2b88be1..a9370176 100644 --- a/.gitignore +++ b/.gitignore @@ -197,4 +197,5 @@ node_modules/ # BenchmarkDotNet output BenchmarkDotNet.Artifacts/!scripts/* -!build/ \ No newline at end of file +!build/ +.tools/ diff --git a/Directory.Packages.props b/Directory.Packages.props index ece5b75c..c6d17db5 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -10,16 +10,9 @@ 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.30 - 3.2.8 + 1.0.0-prerelease.33 - - - - - - diff --git a/Justfile b/Justfile index cb8b8d78..977113cf 100644 --- a/Justfile +++ b/Justfile @@ -5,8 +5,9 @@ solution_file := root_folder + "Telemetry.SourceGenerator.slnx" test_solution := solution_file build_configuration := "Release" -pipeline_solution := "build/Pipeline.slnx" -pipeline_project := "build/PipelineCLI/PipelineCLI.csproj" +pipeline_version := "0.2.1" +pipeline_feed := "https://api.nuget.org/v3/index.json" +pipeline_tool := ".tools/purview-build/purview-build" sample_solution_file := "./samples/SampleApp/SampleApp.slnx" artifact_folder := "./artifacts/" @@ -18,38 +19,50 @@ benchmark_solution := "./benchmarks/Purview.Telemetry.Benchmarks/Purview.Telemet default: just --list +# Install the shared Purview.Build tool (authenticated to the Purview-Dev feed) if not present +[private] +ensure-pipeline-tool: + if [ ! -x "{{ pipeline_tool }}" ]; then \ + dotnet tool install Purview.Build --tool-path .tools/purview-build --add-source "{{ pipeline_feed }}" --version "{{ pipeline_version }}"; \ + fi + # Run the PR pipeline (restore, build, lint, tests) [group('Pipeline')] pipeline-pr *args: + just ensure-pipeline-tool echo "Running PR pipeline..." - dotnet run --project {{ pipeline_project }} --configuration {{ build_configuration }} {{ args }} + "{{ pipeline_tool }}" {{ args }} # Run the build pipeline (restore, build, lint) [group('Pipeline')] pipeline-build *args: + just ensure-pipeline-tool echo "Running build pipeline..." - dotnet run --project {{ pipeline_project }} --configuration {{ build_configuration }} -- --Build:RunTests=false --Release:Mode=None {{ args }} + "{{ pipeline_tool }}" --Build:RunTests=false --Release:Mode=None {{ args }} # Run the release pipeline (restore, build, lint, tests, pack, publish, GitHub release) [group('Pipeline')] pipeline-release *args: + just ensure-pipeline-tool echo "Running release pipeline..." - dotnet run --project {{ pipeline_project }} --configuration {{ build_configuration }} -- --Release:Mode=NuGet {{ args }} + "{{ pipeline_tool }}" --Release:Mode=NuGet {{ args }} # Run the release pipeline (restore, build, lint, tests, pack, local nuget publish) # Note: `just` runs recipes through the shell, which strips backslashes from unquoted arguments. -# Always use forward slashes for the feed path, e.g. +# Use the LOCAL_NUGET_FEED_PATH environment variable or forward slashes, e.g. # just pipeline-local-release --PublishLocalNuGet:LocalFeedPath=p:/_sync-projects/.local-nuget/ [group('Pipeline')] pipeline-local-release *args: + just ensure-pipeline-tool echo "Running local release pipeline..." - dotnet run --project {{ pipeline_project }} --configuration {{ build_configuration }} -- --Release:Mode=LocalNuGet {{ args }} + "{{ pipeline_tool }}" --Release:Mode=LocalNuGet {{ args }} # Run the pipeline with tests enabled [group('Pipeline')] pipeline-tests *args: + just ensure-pipeline-tool echo "Running tests pipeline..." - dotnet run --project {{ pipeline_project }} --configuration {{ build_configuration }} -- --Build:RunTests=true --Release:Mode=None {{ args }} + "{{ pipeline_tool }}" --Build:RunTests=true --Release:Mode=None {{ args }} # ----------------------------------------------------------------------------- # Build and Test @@ -156,11 +169,6 @@ vs: echo "Opening {{ BLUE }}{{ solution_file }}{{ NORMAL }}..." open "{{ solution_file }}" -# Open the solution in Visual Studio/ Registered application -[group('Utilities')] -vs-pipeline: - open {{ pipeline_solution }} - # Opens the root folder in Visual Studio Code [group('System/ Shell')] diff --git a/README.md b/README.md index 337da4cb..0fbfdaad 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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 telemetry from methods you define on an interface. -[![CI](https://github.com/purview-dev/telemetry-sourcegenerator/actions/workflows/ci.yml/badge.svg)](https://github.com/purview-dev/telemetry-sourcegenerator/actions/workflows/ci.yml) +[![Release](https://github.com/purview-dev/telemetry-sourcegenerator/actions/workflows/release.yml/badge.svg)](https://github.com/purview-dev/telemetry-sourcegenerator/actions/workflows/release.yml) ## Features diff --git a/build/Directory.Build.props b/build/Directory.Build.props deleted file mode 100644 index f89239e0..00000000 --- a/build/Directory.Build.props +++ /dev/null @@ -1,12 +0,0 @@ - - - Purview.Telemetry.SourceGenerator - true - - - - - - $(NoWarn);CA1062;CA1515;CA2007;CA1873; - - diff --git a/build/Directory.Build.targets b/build/Directory.Build.targets deleted file mode 100644 index a3bbd31a..00000000 --- a/build/Directory.Build.targets +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/build/Pipeline.slnx b/build/Pipeline.slnx deleted file mode 100644 index 410fbba4..00000000 --- a/build/Pipeline.slnx +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/build/PipelineCLI/GlobalUsings.cs b/build/PipelineCLI/GlobalUsings.cs deleted file mode 100644 index 992ea97f..00000000 --- a/build/PipelineCLI/GlobalUsings.cs +++ /dev/null @@ -1,11 +0,0 @@ -global using Microsoft.Extensions.Configuration; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Options; -global using ModularPipelines; -global using ModularPipelines.Extensions; -global using Octokit; -global using Octokit.Internal; -global using Purview.Telemetry.SourceGenerator.PipelineCLI.Helpers; -global using Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; -global using Purview.Telemetry.SourceGenerator.PipelineCLI.Settings; diff --git a/build/PipelineCLI/Helpers/DotNetCLIOptions.cs b/build/PipelineCLI/Helpers/DotNetCLIOptions.cs deleted file mode 100644 index 0fbf697c..00000000 --- a/build/PipelineCLI/Helpers/DotNetCLIOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using ModularPipelines.Options; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Helpers; - -public sealed record DotNetCLIOptions : CommandLineToolOptions -{ - public static DotNetCLIOptions Create(params string[] commandParts) => - new() { Tool = "dotnet", CommandParts = commandParts }; -} diff --git a/build/PipelineCLI/Helpers/PathHelpers.cs b/build/PipelineCLI/Helpers/PathHelpers.cs deleted file mode 100644 index be5485e5..00000000 --- a/build/PipelineCLI/Helpers/PathHelpers.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Helpers; - -static class PathHelpers -{ - public static string FindRepositoryRoot(string? startDirectory = null) - { - if (string.IsNullOrEmpty(startDirectory)) - startDirectory = PipelineProjectDirectory.Find(); - - DirectoryInfo? directory = new(startDirectory); - while (directory is not null) - { - if (File.Exists(Path.Combine(directory.FullName, "package.json"))) - return directory.FullName; - - directory = directory.Parent; - } - - throw new InvalidOperationException("Could not locate the repository root (no package.json found)."); - } -} diff --git a/build/PipelineCLI/Helpers/TestHelpers.cs b/build/PipelineCLI/Helpers/TestHelpers.cs deleted file mode 100644 index 0ae4f0d6..00000000 --- a/build/PipelineCLI/Helpers/TestHelpers.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Helpers; - -static class TestHelpers -{ - public static string BuildTUnitTreeNodeFilter( - string? assembly = null, - string? @namespace = null, - string? className = null, - string? testNameQuery = null - ) - { - var filter = "/"; - filter += assembly switch - { - null => "*", - _ => assembly, - }; - - filter += @namespace switch - { - null => "*", - _ => @namespace, - }; - - filter += className switch - { - null => "*", - _ => className, - }; - - filter += testNameQuery switch - { - null => "*", - _ => testNameQuery, - }; - - return filter; - } -} diff --git a/build/PipelineCLI/Modules/BuildModule.cs b/build/PipelineCLI/Modules/BuildModule.cs deleted file mode 100644 index ecca6c5d..00000000 --- a/build/PipelineCLI/Modules/BuildModule.cs +++ /dev/null @@ -1,30 +0,0 @@ -using ModularPipelines.Attributes; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Build")] -[DependsOn] -public class BuildModule(IOptions settings) : Module -{ - protected override async Task ExecuteAsync( - IModuleContext context, - CancellationToken cancellationToken - ) - { - return await context - .DotNet() - .Build( - new() - { - ProjectSolution = settings.Value.Solution, - Configuration = settings.Value.Configuration, - NoRestore = true, - }, - cancellationToken: cancellationToken - ); - } -} diff --git a/build/PipelineCLI/Modules/CreateGitHubReleaseModule.cs b/build/PipelineCLI/Modules/CreateGitHubReleaseModule.cs deleted file mode 100644 index 97aa0502..00000000 --- a/build/PipelineCLI/Modules/CreateGitHubReleaseModule.cs +++ /dev/null @@ -1,54 +0,0 @@ -using ModularPipelines.Attributes; -using ModularPipelines.Configuration; -using ModularPipelines.Context; -using ModularPipelines.GitHub.Extensions; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Release")] -[DependsOn] -[DependsOn] -public class CreateGitHubReleaseModule(IOptions releaseSettings, IOptions gitSettings) - : Module -{ - protected override ModuleConfiguration Configure() => - ModuleConfiguration - .Create() - .WithSkipWhen(_ => - releaseSettings.Value.Mode is not (ReleaseMode.NuGet or ReleaseMode.GitHubRelease) - || string.IsNullOrWhiteSpace(gitSettings.Value.GetGitHubToken()) - ? SkipDecision.Skip( - "GitHub release creation is disabled. Set Release__Mode=NuGet (or GitHubRelease) and GITHUB_TOKEN to create a GitHub release." - ) - : SkipDecision.DoNotSkip - ) - .Build(); - - protected override async Task ExecuteAsync(IModuleContext context, CancellationToken cancellationToken) - { - var versionResult = await context.GetModule(); - var version = - versionResult.ValueOrDefault - ?? throw new InvalidOperationException("The version was not produced by the version module."); - - var tag = $"v{version}"; - - var repositoryIdString = context.GitHub().EnvironmentVariables.RepositoryId; - if (!long.TryParse(repositoryIdString, out var repositoryId)) - { - throw new InvalidOperationException( - $"Failed to parse RepositoryId '{repositoryIdString}' as a valid long integer." - ); - } - - // Create a new release on GitHub with the specified tag and generate release notes - return await context - .GitHub() - .Client.Repository.Release.Create( - repositoryId, - new NewRelease(tag) { Name = tag, GenerateReleaseNotes = true } - ); - } -} diff --git a/build/PipelineCLI/Modules/LintModule.cs b/build/PipelineCLI/Modules/LintModule.cs deleted file mode 100644 index 8d18f15d..00000000 --- a/build/PipelineCLI/Modules/LintModule.cs +++ /dev/null @@ -1,45 +0,0 @@ -using ModularPipelines.Attributes; -using ModularPipelines.Configuration; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Build")] -public sealed class LintModule(IOptions settings) : Module -{ - protected override ModuleConfiguration Configure() => - ModuleConfiguration - .Create() - .WithSkipWhen(_ => - settings.Value.RunLint - ? SkipDecision.DoNotSkip - : SkipDecision.Skip("Linting is disabled. Set Build__RunLint=true to enable it.") - ) - .Build(); - - protected override async Task ExecuteAsync( - IModuleContext context, - CancellationToken cancellationToken - ) - { - var repositoryRoot = PathHelpers.FindRepositoryRoot(); - var dotnet = context.DotNet(); - var restoreResult = await dotnet.Tool.Restore( - new() { Interactive = false, ToolManifest = Path.Combine(repositoryRoot, ".config", "dotnet-tools.json") }, - new() { WorkingDirectory = repositoryRoot }, - cancellationToken - ); - if (restoreResult.ExitCode != 0) - return restoreResult; - - // Restore worked, now run the linter - return await context.Shell.Command.ExecuteCommandLineTool( - DotNetCLIOptions.Create("tool", "run", "csharpier", "check", repositoryRoot), - new() { WorkingDirectory = repositoryRoot }, - cancellationToken: cancellationToken - ); - } -} diff --git a/build/PipelineCLI/Modules/PackModule.cs b/build/PipelineCLI/Modules/PackModule.cs deleted file mode 100644 index 779ad130..00000000 --- a/build/PipelineCLI/Modules/PackModule.cs +++ /dev/null @@ -1,52 +0,0 @@ -using ModularPipelines.Attributes; -using ModularPipelines.Configuration; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.DotNet.Options; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Build")] -[DependsOn] -[DependsOn] -public sealed class PackModule(IOptions settings) : Module -{ - protected override ModuleConfiguration Configure() => - ModuleConfiguration - .Create() - .WithSkipWhen(_ => - !settings.Value.RunPack - ? SkipDecision.Skip("Packing is disabled. Set Build__RunPack=true to enable it.") - : SkipDecision.DoNotSkip - ) - .Build(); - - protected override async Task ExecuteAsync( - IModuleContext context, - CancellationToken cancellationToken - ) - { - var versionResult = await context.GetModule(); - var nugetVersion = - versionResult.ValueOrDefault - ?? throw new InvalidOperationException("The version was not produced by the version module."); - - Directory.CreateDirectory(settings.Value.ArtifactsFolder); - - var version = nugetVersion.ToString(); - return await context - .DotNet() - .Pack( - new DotNetPackOptions - { - ProjectSolution = settings.Value.Solution, - Configuration = settings.Value.Configuration, - Output = settings.Value.ArtifactsFolder, - Properties = [("PackageVersion", version), ("Version", version)], - }, - cancellationToken: cancellationToken - ); - } -} diff --git a/build/PipelineCLI/Modules/PublishLocalNuGetModule.cs b/build/PipelineCLI/Modules/PublishLocalNuGetModule.cs deleted file mode 100644 index e25949a4..00000000 --- a/build/PipelineCLI/Modules/PublishLocalNuGetModule.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using ModularPipelines.Attributes; -using ModularPipelines.Configuration; -using ModularPipelines.Context; -using ModularPipelines.Models; -using ModularPipelines.Modules; -using NuGet.Versioning; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Build")] -[DependsOn] -public class PublishLocalNuGetModule( - IOptions localNuGetFeedSettings, - IOptions releaseSettings, - IOptions buildSettings -) : Module -{ - protected override ModuleConfiguration Configure() => - ModuleConfiguration - .Create() - .WithSkipWhen(ctx => - !ctx.IsRunningLocally() || releaseSettings.Value.Mode != ReleaseMode.LocalNuGet - ? SkipDecision.Skip( - "Local NuGet Feed publishing is disabled. Run the pipeline locally with Release__Mode=LocalNuGet to enable it." - ) - : SkipDecision.DoNotSkip - ) - .Build(); - - protected override async Task ExecuteAsync( - IModuleContext context, - CancellationToken cancellationToken - ) - { - var localFeedPath = localNuGetFeedSettings.Value.LocalFeedPath; - - var validationResults = new List(); - var validationContext = new ValidationContext(localNuGetFeedSettings.Value); - if ( - !Validator.TryValidateObject( - localNuGetFeedSettings.Value, - validationContext, - validationResults, - validateAllProperties: true - ) - ) - { - foreach (var validationResult in validationResults) - context.Logger.LogError("{Message}", validationResult.ErrorMessage); - - throw new InvalidOperationException( - $"Invalid {nameof(PublishLocalNuGetSettings)} configuration for {nameof(PublishLocalNuGetSettings.LocalFeedPath)}. " - + "Windows paths with backslashes may have been stripped by the shell; " - + "use forward slashes, e.g. --PublishLocalNuGet:LocalFeedPath=p:/_sync-projects/.local-nuget/." - ); - } - - var fullLocalFeedPath = Path.GetFullPath(localFeedPath); - context.Logger.LogInformation("Publishing local NuGet packages to {LocalFeedPath}.", fullLocalFeedPath); - - if (!Directory.Exists(fullLocalFeedPath)) - Directory.CreateDirectory(fullLocalFeedPath); - - var packages = Directory - .GetFiles(buildSettings.Value.ArtifactsFolder, "*.nupkg") - .Concat(Directory.GetFiles(buildSettings.Value.ArtifactsFolder, "*.snupkg")) - .ToArray(); - if (packages.Length == 0) - { - throw new InvalidOperationException( - $"No packages found in {buildSettings.Value.ArtifactsFolder}. The local feed was not populated." - ); - } - - List nupkgPackages = []; - foreach (var package in packages) - { - var fileName = Path.GetFileName(package); - var destinationPath = Path.Combine(fullLocalFeedPath, fileName); - - if (Path.GetExtension(fileName) == ".nupkg") - nupkgPackages.Add(await ParsePackageDetailsAsync(package, cancellationToken)); - - if (!localNuGetFeedSettings.Value.OverwriteExistingPackages && File.Exists(destinationPath)) - { - context.Logger.LogInformation("Package {Package} already exists in local feed. Skipping.", fileName); - File.Delete(package); - - continue; - } - - File.Move(package, destinationPath, true); - context.Logger.LogInformation("Copied package {Package} to local feed.", fileName); - } - - if (localNuGetFeedSettings.Value.ClearPackageCache) - { - context.Logger.LogInformation("Clearing local NuGet package cache..."); - - var globalPackagesResult = await context.Shell.Command.ExecuteCommandLineTool( - DotNetCLIOptions.Create("nuget", "locals", "global-packages", "--list"), - cancellationToken: cancellationToken - ); - if (globalPackagesResult.ExitCode != 0) - return globalPackagesResult; - - var httpCacheResult = await context.Shell.Command.ExecuteCommandLineTool( - DotNetCLIOptions.Create("nuget", "locals", "http-cache", "--list"), - cancellationToken: cancellationToken - ); - if (httpCacheResult.ExitCode != 0) - return httpCacheResult; - - var globalPackagePaths = globalPackagesResult - .StandardOutput.Replace("global-packages: ", "", StringComparison.Ordinal) - .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) - .Where(Directory.Exists); - - var httpCachePaths = httpCacheResult - .StandardOutput.Replace("http-cache: ", "", StringComparison.Ordinal) - .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) - .Where(Directory.Exists); - - foreach (var artifact in nupkgPackages) - { -#pragma warning disable CA1308 // Normalize strings to uppercase - var loweredPackageId = artifact.PackageId.ToLowerInvariant(); - var loweredVersion = artifact.Version.ToFullString().ToLowerInvariant(); -#pragma warning restore CA1308 // Normalize strings to uppercase - - foreach (var globalPath in globalPackagePaths) - { - var packagePath = Path.Combine(globalPath, loweredPackageId, artifact.Version.ToFullString()); - if (Directory.Exists(packagePath)) - { - Directory.Delete(packagePath, true); - context.Logger.LogInformation( - "Deleted package {Package} version {Version} from global packages cache.", - artifact.PackageId, - artifact.Version - ); - } - } - foreach (var httpCachePath in httpCachePaths) - { - string[] packagePaths = - [ - Path.Combine(httpCachePath, "list_" + loweredPackageId + ".dat"), - Path.Combine(httpCachePath, "list_" + loweredPackageId + "_index.dat"), - Path.Combine(httpCachePath, "list_" + loweredPackageId + "_range_*.dat"), - Path.Combine(httpCachePath, "nupkg_" + loweredPackageId + "." + loweredVersion + ".dat"), - ]; - - foreach (var path in packagePaths) - { - var directory = Path.GetDirectoryName(path); - var pattern = Path.GetFileName(path); - foreach (var file in Directory.EnumerateFiles(directory!, pattern, SearchOption.AllDirectories)) - { - File.Delete(file); - context.Logger.LogInformation( - "Deleted package {Package} version {Version} from HTTP cache.", - artifact.PackageId, - artifact.Version - ); - } - } - } - } - } - - if (localNuGetFeedSettings.Value.ShutdownDotnetBuilderServer) - { - context.Logger.LogInformation("Shutting down dotnet builder server..."); - - return await context.Shell.Command.ExecuteCommandLineTool( - DotNetCLIOptions.Create("build-server", "shutdown"), - cancellationToken: cancellationToken - ); - } - - return null; - } - - static async Task ParsePackageDetailsAsync(string artifact, CancellationToken cancellationToken) - { - using var packageReader = new NuGet.Packaging.PackageArchiveReader(artifact); - var packaging = await packageReader.GetNuspecReaderAsync(cancellationToken); - - return new(packaging.GetId(), packaging.GetVersion()); - } -} - -record struct PackageDetails(string PackageId, NuGetVersion Version); diff --git a/build/PipelineCLI/Modules/PublishNuGetModule.cs b/build/PipelineCLI/Modules/PublishNuGetModule.cs deleted file mode 100644 index 43253275..00000000 --- a/build/PipelineCLI/Modules/PublishNuGetModule.cs +++ /dev/null @@ -1,70 +0,0 @@ -using ModularPipelines.Attributes; -using ModularPipelines.Configuration; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Release")] -[DependsOn] -[DependsOn] -public class PublishNuGetModule( - IOptions buildSettings, - IOptions nugetSettings, - IOptions releaseSettings -) : Module -{ - protected override ModuleConfiguration Configure() => - ModuleConfiguration - .Create() - .WithSkipWhen(_ => - releaseSettings.Value.Mode != ReleaseMode.NuGet - || string.IsNullOrWhiteSpace(nugetSettings.Value.GetNuGetAPIKey()) - ? SkipDecision.Skip( - "NuGet publishing is disabled. Set Release__Mode=NuGet and NuGet__ApiKey (or NUGET_APIKEY) to publish packages to nuget.org." - ) - : SkipDecision.DoNotSkip - ) - .Build(); - - protected override async Task ExecuteAsync( - IModuleContext context, - CancellationToken cancellationToken - ) - { - var artifactsFolder = buildSettings.Value.ArtifactsFolder; - if (!Directory.Exists(artifactsFolder)) - { - throw new InvalidOperationException( - $"The artifacts folder '{artifactsFolder}' does not exist. " - + "Ensure the pack step ran (Release__Mode must not be None) before publishing." - ); - } - - var packages = Directory.EnumerateFiles(artifactsFolder, "*.nupkg", SearchOption.TopDirectoryOnly).ToList(); - - if (packages.Count == 0) - { - throw new InvalidOperationException($"No NuGet packages found in {buildSettings.Value.ArtifactsFolder}."); - } - - var tasks = packages.Select(package => - context - .DotNet() - .Nuget.Push( - new() - { - Path = package, - Source = nugetSettings.Value.FeedUrl, - ApiKey = nugetSettings.Value.GetNuGetAPIKey(), - SkipDuplicate = true, - }, - cancellationToken: cancellationToken - ) - ); - - return await Task.WhenAll(tasks); - } -} diff --git a/build/PipelineCLI/Modules/RestoreModule.cs b/build/PipelineCLI/Modules/RestoreModule.cs deleted file mode 100644 index 6af1a9f2..00000000 --- a/build/PipelineCLI/Modules/RestoreModule.cs +++ /dev/null @@ -1,25 +0,0 @@ -using ModularPipelines.Attributes; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.DotNet.Options; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Build")] -public class RestoreModule(IOptions settings) : Module -{ - protected override async Task ExecuteAsync( - IModuleContext context, - CancellationToken cancellationToken - ) - { - return await context - .DotNet() - .Restore( - new DotNetRestoreOptions { ProjectSolution = settings.Value.Solution }, - cancellationToken: cancellationToken - ); - } -} diff --git a/build/PipelineCLI/Modules/RunTestsModule.cs b/build/PipelineCLI/Modules/RunTestsModule.cs deleted file mode 100644 index ca507df2..00000000 --- a/build/PipelineCLI/Modules/RunTestsModule.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Diagnostics; -using System.Text.RegularExpressions; -using ModularPipelines.Attributes; -using ModularPipelines.Configuration; -using ModularPipelines.Context; -using ModularPipelines.DotNet.Extensions; -using ModularPipelines.DotNet.Options; -using ModularPipelines.Models; -using ModularPipelines.Modules; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Build")] -[DependsOn] -public class RunTestsModule(IOptions settings) : Module -{ - protected override ModuleConfiguration Configure() => - ModuleConfiguration - .Create() - .WithSkipWhen(_ => - settings.Value.RunTests - ? SkipDecision.DoNotSkip - : SkipDecision.Skip("Tests are disabled. Set Build__RunTests=true to run them.") - ) - .Build(); - - protected override async Task ExecuteAsync( - IModuleContext context, - CancellationToken cancellationToken - ) - { - var testProjects = FilterTestProjects( - Directory.EnumerateFiles("src/tests", "*Tests.csproj", SearchOption.AllDirectories).ToList(), - settings.Value.TestProjects - ); - if (testProjects.Count == 0) - { - context.Logger.LogWarning( - "No test projects matched 'src/tests' (filter: {TestProjects}), despite tests being enabled. Skipping test execution.", - settings.Value.TestProjects - ); - - return []; - } - - var timings = new List<(string Project, TimeSpan Elapsed, int ExitCode)>(); - - var tasks = testProjects.Select(async project => - { - var stopwatch = Stopwatch.StartNew(); - var result = await context - .DotNet() - .Test( - new DotNetTestOptions - { - Project = project, - Configuration = settings.Value.Configuration, - NoBuild = true, - NoRestore = true, - Arguments = ["--ignore-exit-code", "8", "--treenode-filter", settings.Value.TestFilter], - }, - cancellationToken: cancellationToken - ); - stopwatch.Stop(); - - lock (timings) - timings.Add((project, stopwatch.Elapsed, result.ExitCode)); - - return result; - }); - - var results = await Task.WhenAll(tasks); - - context.Logger.LogInformation( - "Test run timings:{NewLine}{Timings}", - Environment.NewLine, - string.Join( - Environment.NewLine, - timings - .OrderByDescending(t => t.Elapsed) - .Select(t => $" {Path.GetFileName(t.Project)}: {t.Elapsed.TotalSeconds:F1}s (exit {t.ExitCode})") - ) - ); - - return results; - } - - static IReadOnlyList FilterTestProjects(IReadOnlyList projects, string filter) - { - if (string.IsNullOrWhiteSpace(filter) || filter.Trim() == "*") - return projects; - - var patterns = filter - .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - .Select(ToRegexPattern) - .ToArray(); - - return projects - .Where(project => - { - var fileName = Path.GetFileName(project); - return patterns.Any(pattern => Regex.IsMatch(fileName, pattern, RegexOptions.IgnoreCase)); - }) - .ToList(); - } - - static string ToRegexPattern(string entry) - { - if (entry.Contains('*', StringComparison.Ordinal)) - { - var escaped = Regex.Escape(entry); - return "^" + escaped.Replace("\\*", ".*", StringComparison.Ordinal) + "$"; - } - - return "^" + Regex.Escape(entry) + "$"; - } -} diff --git a/build/PipelineCLI/Modules/VersionModule.cs b/build/PipelineCLI/Modules/VersionModule.cs deleted file mode 100644 index 6aa3af2e..00000000 --- a/build/PipelineCLI/Modules/VersionModule.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Text.Json; -using ModularPipelines.Attributes; -using ModularPipelines.Context; -using ModularPipelines.Modules; -using NuGet.Versioning; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Modules; - -[ModuleCategory("Build")] -public class VersionModule : Module -{ - protected override async Task ExecuteAsync( - IModuleContext context, - CancellationToken cancellationToken - ) - { - var packageJsonPath = Path.Combine(Environment.CurrentDirectory, "package.json"); - - if (!File.Exists(packageJsonPath)) - throw new FileNotFoundException($"Could not find package.json at {packageJsonPath}"); - - var packageJson = await File.ReadAllTextAsync(packageJsonPath, cancellationToken); - - using var document = JsonDocument.Parse(packageJson); - var version = document.RootElement.GetProperty("version").GetString(); - - if (string.IsNullOrWhiteSpace(version)) - throw new InvalidOperationException("The version field in package.json is missing or empty."); - - if (!NuGetVersion.TryParse(version, out var nugetVersion)) - throw new InvalidOperationException($"The version '{version}' in package.json is not a valid SemVer."); - - context.Summary.KeyValue("Version", "Package version", version); - return nugetVersion; - } -} diff --git a/build/PipelineCLI/PipelineCLI.csproj b/build/PipelineCLI/PipelineCLI.csproj deleted file mode 100644 index 0264da81..00000000 --- a/build/PipelineCLI/PipelineCLI.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - PreserveNewest - - - diff --git a/build/PipelineCLI/PipelineProjectDirectory.cs b/build/PipelineCLI/PipelineProjectDirectory.cs deleted file mode 100644 index 78229629..00000000 --- a/build/PipelineCLI/PipelineProjectDirectory.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI; - -static class PipelineProjectDirectory -{ - const string DirectoryVariable = "MODULAR_PIPELINES_DIRECTORY"; - - public static string Find([CallerFilePath] string sourceFilePath = "") - { - var configuredDirectory = Environment.GetEnvironmentVariable(DirectoryVariable); - if (!string.IsNullOrWhiteSpace(configuredDirectory)) - { - return ValidateConfiguredDirectory(configuredDirectory); - } - - var sourceDirectory = Path.GetDirectoryName(sourceFilePath); - return IsPipelineDirectory(sourceDirectory) ? sourceDirectory! : FindFromBuildOutput(); - } - - static string ValidateConfiguredDirectory(string configuredDirectory) - { - var fullPath = Path.GetFullPath(configuredDirectory); - return IsPipelineDirectory(fullPath) - ? fullPath - : throw new InvalidOperationException( - $"{DirectoryVariable} must point to a directory containing appsettings.json and a project file." - ); - } - - static string FindFromBuildOutput() - { - for ( - var directory = new DirectoryInfo(AppContext.BaseDirectory); - directory is not null; - directory = directory.Parent - ) - { - if (IsPipelineDirectory(directory.FullName)) - { - return directory.FullName; - } - } - - throw new InvalidOperationException( - $"Could not locate the pipeline project directory. Set {DirectoryVariable} to its path." - ); - } - - static bool IsPipelineDirectory(string? directory) => - directory is not null - && Directory.Exists(directory) - && File.Exists(Path.Combine(directory, "appsettings.json")) - && Directory.EnumerateFiles(directory, "*.csproj").Any(); -} diff --git a/build/PipelineCLI/Program.cs b/build/PipelineCLI/Program.cs deleted file mode 100644 index 5d38362a..00000000 --- a/build/PipelineCLI/Program.cs +++ /dev/null @@ -1,42 +0,0 @@ -var pipelineDirectory = PipelineProjectDirectory.Find(); -var repositoryRoot = PathHelpers.FindRepositoryRoot(pipelineDirectory); - -var builder = Pipeline.CreateBuilder(args); - -builder - .Configuration.AddJsonFile(Path.Combine(pipelineDirectory, "appsettings.json"), optional: false) - .AddEnvironmentVariables() - .AddCommandLine(args); - -builder.Services.Configure(builder.Configuration.GetSection(BuildSettings.SectionName)); -builder.Services.Configure(builder.Configuration.GetSection(NuGetSettings.SectionName)); -builder.Services.Configure( - builder.Configuration.GetSection(PublishLocalNuGetSettings.SectionName) -); -builder.Services.Configure(builder.Configuration.GetSection(GitHubSettings.SectionName)); -builder.Services.Configure(builder.Configuration.GetSection(ReleaseSettings.SectionName)); - -builder.Services.AddSingleton(serviceProvider => -{ - var settings = serviceProvider.GetRequiredService>(); - var accessToken = settings.Value.GetGitHubToken(); - - return new GitHubClient(new(settings.Value.ProductHeader), new InMemoryCredentialStore(new(accessToken))); -}); - -Environment.CurrentDirectory = repositoryRoot; - -builder - .AddModule() - .AddModule() - .AddModule() - .AddModule() - .AddModule() - .AddModule() - .AddModule() - .AddModule() - .AddModule(); - -await using var pipeline = await builder.BuildAsync(); - -await pipeline.RunAsync(); diff --git a/build/PipelineCLI/Properties/launchSettings.json b/build/PipelineCLI/Properties/launchSettings.json deleted file mode 100644 index d5bbebe5..00000000 --- a/build/PipelineCLI/Properties/launchSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "profiles": { - "Run": { - "commandName": "Project" - }, - "Local-NuGet": { - "commandName": "Project", - "commandLineArgs": "--Release:Mode=LocalNuGet\r\n--PublishLocalNuGet:LocalFeedPath=p:\\_sync-projects\\.local-nuget\\" - } - } -} diff --git a/build/PipelineCLI/Settings/BuildSettings.cs b/build/PipelineCLI/Settings/BuildSettings.cs deleted file mode 100644 index ca5beced..00000000 --- a/build/PipelineCLI/Settings/BuildSettings.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Settings; - -public sealed class BuildSettings -{ - public const string SectionName = "Build"; - - public LogLevel LogLevel { get; init; } = LogLevel.Warning; - - [Required(AllowEmptyStrings = false)] - public string Solution { get; init; } = "src/Telemetry.SourceGenerator.slnx"; - - [Required(AllowEmptyStrings = false)] - public string Configuration { get; init; } = "Release"; - - [Required(AllowEmptyStrings = false)] - public string ArtifactsFolder { get; init; } = "artifacts"; - - public bool RunTests { get; init; } = true; - - [Required(AllowEmptyStrings = false)] - public string TestFilter { get; init; } = "/*/*/*/*/"; - - /// - /// Comma-separated list of test project file names (or glob patterns) to run. - /// Empty or "*" runs every test project under src/tests. - /// - public string TestProjects { get; init; } = "*"; - - public bool RunLint { get; init; } = true; - - public bool RunPack { get; init; } = true; -} diff --git a/build/PipelineCLI/Settings/GitHubSettings.cs b/build/PipelineCLI/Settings/GitHubSettings.cs deleted file mode 100644 index c8cdfe55..00000000 --- a/build/PipelineCLI/Settings/GitHubSettings.cs +++ /dev/null @@ -1,32 +0,0 @@ -using ModularPipelines.Attributes; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Settings; - -public sealed record GitHubSettings -{ - public const string SectionName = "GitHub"; - - [SecretValue] - public string? AccessToken { get; init; } - - [SecretValue] - [ConfigurationKeyName("GITHUB_TOKEN")] - public string? EnvAccessToken { get; init; } - - public string ProductHeader { get; init; } = "Purview.Telemetry.SourceGenerator.Pipeline"; - - public string? GetGitHubToken() - { - if (!string.IsNullOrWhiteSpace(AccessToken)) - return AccessToken; - - if (!string.IsNullOrWhiteSpace(EnvAccessToken)) - return EnvAccessToken; - - // GitHub Actions provisions the automatic GITHUB_TOKEN as a plain environment variable. - // The config binder keys it under the "GitHub" section (GitHub:GITHUB_TOKEN), which the - // standard GITHUB_TOKEN env var does not map to, so read it directly as a fallback. - var processToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); - return string.IsNullOrWhiteSpace(processToken) ? null : processToken; - } -} diff --git a/build/PipelineCLI/Settings/NuGetSettings.cs b/build/PipelineCLI/Settings/NuGetSettings.cs deleted file mode 100644 index 3f4b475e..00000000 --- a/build/PipelineCLI/Settings/NuGetSettings.cs +++ /dev/null @@ -1,22 +0,0 @@ -using ModularPipelines.Attributes; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Settings; - -public sealed record NuGetSettings -{ - public const string SectionName = "NuGet"; - - [SecretValue] - public string? APIKey { get; set; } - - [SecretValue] - [ConfigurationKeyName("NUGET_APIKEY")] - public string? EnvAPIKey { get; set; } - - public string FeedUrl { get; init; } = "https://api.nuget.org/v3/index.json"; - - public string? GetNuGetAPIKey() => - !string.IsNullOrWhiteSpace(APIKey) ? APIKey - : !string.IsNullOrWhiteSpace(EnvAPIKey) ? EnvAPIKey - : null; -} diff --git a/build/PipelineCLI/Settings/PublishLocalNuGetSettings.cs b/build/PipelineCLI/Settings/PublishLocalNuGetSettings.cs deleted file mode 100644 index 67cf8df5..00000000 --- a/build/PipelineCLI/Settings/PublishLocalNuGetSettings.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Settings; - -public sealed record PublishLocalNuGetSettings : IValidatableObject -{ - public const string SectionName = "PublishLocalNuGet"; - - [Required(AllowEmptyStrings = false)] - public string LocalFeedPath { get; init; } = string.Empty; - - public bool OverwriteExistingPackages { get; init; } = true; - - public bool ShutdownDotnetBuilderServer { get; init; } = true; - - public bool ClearPackageCache { get; init; } = true; - - public IEnumerable Validate(ValidationContext validationContext) - { - if (string.IsNullOrWhiteSpace(LocalFeedPath)) - { - yield return new ValidationResult("LocalFeedPath is required.", [nameof(LocalFeedPath)]); - yield break; - } - - // Path.IsPathRooted("p:foo") returns true, but a drive-relative path like "p:foo" is NOT an - // absolute path: Path.GetFullPath resolves it against the current directory and can silently - // copy packages to an unintended location. This is the classic signature of a Windows path whose - // backslashes were stripped by a sh-style shell, e.g. 'p:\_sync-projects\.local-nuget\'. - if (LocalFeedPath.Length >= 2 && LocalFeedPath[1] == ':') - { - var hasSeparatorAfterDrive = - LocalFeedPath.Length >= 3 - && ( - LocalFeedPath[2] == Path.DirectorySeparatorChar - || LocalFeedPath[2] == Path.AltDirectorySeparatorChar - ); - if (!hasSeparatorAfterDrive) - { - yield return new ValidationResult( - $"LocalFeedPath '{LocalFeedPath}' is drive-relative, not an absolute path. " - + "This is usually caused by the shell stripping backslashes from a Windows path such as " - + $"'p:\\_sync-projects\\.local-nuget\\'. Use forward slashes instead, e.g. " - + "'p:/_sync-projects/.local-nuget/'.", - [nameof(LocalFeedPath)] - ); - yield break; - } - } - - if (!Path.IsPathRooted(LocalFeedPath)) - { - yield return new ValidationResult( - $"LocalFeedPath must be an absolute path. Received: '{LocalFeedPath}'.", - [nameof(LocalFeedPath)] - ); - yield break; - } - - var root = Path.GetPathRoot(LocalFeedPath); - if (string.IsNullOrEmpty(root)) - { - yield return new ValidationResult( - $"LocalFeedPath could not be parsed. Received: '{LocalFeedPath}'.", - [nameof(LocalFeedPath)] - ); - yield break; - } - - var lastChar = root[^1]; - if (lastChar == Path.DirectorySeparatorChar || lastChar == Path.AltDirectorySeparatorChar) - yield break; - - if (root.StartsWith(@"\\", StringComparison.Ordinal) || root.StartsWith("//", StringComparison.Ordinal)) - yield break; - - yield return new ValidationResult( - $"LocalFeedPath must be an absolute path (e.g. 'C:\\folder' or '\\\\server\\share'). Received: '{LocalFeedPath}'.", - [nameof(LocalFeedPath)] - ); - } -} diff --git a/build/PipelineCLI/Settings/ReleaseSettings.cs b/build/PipelineCLI/Settings/ReleaseSettings.cs deleted file mode 100644 index 77d10f6d..00000000 --- a/build/PipelineCLI/Settings/ReleaseSettings.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Purview.Telemetry.SourceGenerator.PipelineCLI.Settings; - -public enum ReleaseMode -{ - None, - - NuGet, - - GitHubRelease, - - LocalNuGet, -} - -public sealed record ReleaseSettings -{ - public const string SectionName = "Release"; - - public ReleaseMode Mode { get; set; } = ReleaseMode.None; -} diff --git a/build/PipelineCLI/appsettings.json b/build/PipelineCLI/appsettings.json deleted file mode 100644 index b57ed0e0..00000000 --- a/build/PipelineCLI/appsettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "NuGet": { - "FeedUrl": "https://api.nuget.org/v3/index.json" - }, - "GitHub": { - "AccessToken": null, - "ProductHeader": "Purview.Telemetry.SourceGenerator.Pipeline" - }, - "Release": { - "Mode": "None" - } -} diff --git a/package.json b/package.json index 8116fefc..67176199 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "purview-telemetry-sourcegenerator", - "version": "5.0.0-prerelease.3", + "version": "5.0.0-prerelease.4", "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", "repository": { diff --git a/purview-build.json b/purview-build.json new file mode 100644 index 00000000..1f37658e --- /dev/null +++ b/purview-build.json @@ -0,0 +1,11 @@ +{ + "Build": { + "Solution": "src/Telemetry.SourceGenerator.slnx", + "TestRoot": "src/tests", + "TestPatterns": "*Tests.csproj", + "TestFilter": "/*/*/*/*/" + }, + "Release": { + "Mode": "None" + } +} \ No newline at end of file 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 ff5bb01e..94cde4d3 100644 --- a/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/SampleApp.Net48.ConsoleApp.csproj +++ b/samples/SampleApp.Net48/SampleApp.Net48.ConsoleApp/SampleApp.Net48.ConsoleApp.csproj @@ -5,20 +5,15 @@ - + - - - - diff --git a/samples/SampleApp.Net48/SampleApp.Net48.slnx b/samples/SampleApp.Net48/SampleApp.Net48.slnx index 2af29d4e..bb946ae0 100644 --- a/samples/SampleApp.Net48/SampleApp.Net48.slnx +++ b/samples/SampleApp.Net48/SampleApp.Net48.slnx @@ -1,9 +1,17 @@ - + + + + + + diff --git a/samples/SampleApp/README.md b/samples/SampleApp/README.md index 35345f2b..13f861c0 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(TargetsEnum.Activities)] int? count); + [ExcludeTargets(Targets.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(TargetsEnum.Activities)] WeatherForecast[] weatherForecasts); + [ExpandEnumerable(100), ExcludeTargets(Targets.Activities)] WeatherForecast[] weatherForecasts); } ``` diff --git a/samples/SampleApp/SampleApp.APIService/SampleApp.APIService.csproj b/samples/SampleApp/SampleApp.APIService/SampleApp.APIService.csproj index e8bd0b6a..2682e3c1 100644 --- a/samples/SampleApp/SampleApp.APIService/SampleApp.APIService.csproj +++ b/samples/SampleApp/SampleApp.APIService/SampleApp.APIService.csproj @@ -3,21 +3,14 @@ true - + - - - - diff --git a/samples/SampleApp/SampleApp.Web/Clients/IWeatherAPIClientTelemetry.cs b/samples/SampleApp/SampleApp.Web/Clients/IWeatherAPIClientTelemetry.cs index beca8b5d..4020b585 100644 --- a/samples/SampleApp/SampleApp.Web/Clients/IWeatherAPIClientTelemetry.cs +++ b/samples/SampleApp/SampleApp.Web/Clients/IWeatherAPIClientTelemetry.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Net; using Purview.Telemetry; @@ -17,7 +17,7 @@ public interface IWeatherAPIClientTelemetry [Event] [Error] [AutoCounter] - void FailedToGetForecast(Activity? activity, Exception ex, [ExcludeTargets(TargetsEnum.Activities)] int? count); + void FailedToGetForecast(Activity? activity, Exception ex, [ExcludeTargets(Targets.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(TargetsEnum.Activities)] + [ExpandEnumerable(100), ExcludeTargets(Targets.Activities)] #pragma warning disable TSG2008 // Unbounded enumeration possible WeatherForecast[] weatherForecasts ); diff --git a/samples/SampleApp/SampleApp.Web/SampleApp.Web.csproj b/samples/SampleApp/SampleApp.Web/SampleApp.Web.csproj index c8d7f42f..dab79320 100644 --- a/samples/SampleApp/SampleApp.Web/SampleApp.Web.csproj +++ b/samples/SampleApp/SampleApp.Web/SampleApp.Web.csproj @@ -5,21 +5,14 @@ - + - - - - diff --git a/samples/SampleApp/SampleApp.slnx b/samples/SampleApp/SampleApp.slnx index 331d4cd5..f752d7f9 100644 --- a/samples/SampleApp/SampleApp.slnx +++ b/samples/SampleApp/SampleApp.slnx @@ -4,6 +4,14 @@ + + + + + diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ActivityMethods.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ActivityMethods.cs index f2e7c4fd..6dbb1ca6 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ActivityMethods.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ActivityMethods.cs @@ -49,7 +49,7 @@ out var _ var activityVariableName = "activity" + methodTarget.MethodName; - writer.WriteAssignment( + writer.Assignment( TypeLibrary.Activities.SystemDiagnostics.Activity.MakeNullable(writer), activityVariableName, writeValue: assignmentWriter => @@ -106,20 +106,23 @@ out var _ if (methodTarget.Tags.Count > 0 || methodTarget.Baggage.Count > 0) { - writer.NewLine().Write("if (").Write(activityVariableName).WriteLine(" != null)"); - - using (writer.OpenBlockScope()) - { - EmitTagsOrBaggageParameters(writer, activityVariableName, true, methodTarget, false, output); - EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, output); - } + writer + .NewLine() + .IfBlock( + activityVariableName + " != null", + body => + { + EmitTagsOrBaggageParameters(writer, activityVariableName, true, methodTarget, false, output); + EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, output); + } + ); } context.CancellationToken.ThrowIfCancellationRequested(); if (methodTarget.ReturnType.Similar(TypeLibrary.Activities.SystemDiagnostics.Activity)) { - writer.WriteReturn(returnWriter => + writer.Return(returnWriter => returnWriter.Write(activityVariableName).Write(methodTarget.ReturnType.IsNullable ? null : "!") ); } @@ -188,20 +191,16 @@ int kind } } - writer.WriteLine(")"); + writer.Line(")"); } 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())"); - - using (writer.OpenBlockScope()) - { - writer.WriteLine( - "return" + (returnsVoid ? null : " null" + (methodTarget.ReturnType.IsNullable ? null : "!")) + ";" - ); - } + writer.IfBlock( + "!" + PropertyLibrary.Activities.ActivitySourceFieldName + ".HasListeners()", + body => body.Return(returnsVoid ? null : "null" + (methodTarget.ReturnType.IsNullable ? null : "!")) + ); writer.NewLine(); } diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ContextMethods.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ContextMethods.cs index e11c1f95..0be9f6d8 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ContextMethods.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.ContextMethods.cs @@ -52,13 +52,14 @@ out var _ EmitHasListenersTest(writer, methodTarget); - writer.Write("if (").Write(activityVariableName).WriteLine(" != null)"); - - using (writer.OpenBlockScope()) - { - EmitTagsOrBaggageParameters(writer, activityVariableName, true, methodTarget, false, output); - EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, output); - } + writer.IfBlock( + activityVariableName + " != null", + body => + { + 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 a6e33165..6752ecb7 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.EventMethods.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.EventMethods.cs @@ -58,46 +58,53 @@ out var statusDescriptionParam EmitHasListenersTest(writer, methodTarget); - writer.Write("if (").Write(activityVariableName).WriteLine(" != null)"); - - using (writer.OpenBlockScope()) - { - var exceptionParam = - methodTarget.Parameters.FirstOrDefault(m => m.IsException) - ?? methodTarget.Tags.FirstOrDefault(m => m.IsException); - var tagsParameterName = EmitEventTags(writer, methodTarget, activityVariableName, tagsParam, escapeParam); - - var eventVariableName = "activityEvent" + methodTarget.MethodName; - - writer - .NewLine() - .Write(TypeLibrary.Activities.SystemDiagnostics.ActivityEvent) - .Write(' ') - .Write(eventVariableName) - .Write(" = new ") - // Use explicit type for C# 7.3 compatibility (target-typed new() requires C# 9+) - .Write(TypeLibrary.Activities.SystemDiagnostics.ActivityEvent) - .Write("(name: ") - .Write(methodTarget.ActivityOrEventName.Wrap()) - // timestamp: - .Write(", timestamp: ") - .Write(timestampParam?.ParameterName ?? "default") - // tags: - .Write(", tags: ") - .Write(tagsParameterName) - .WriteLine(");"); - - writer.NewLine().Write(activityVariableName).Write(".AddEvent(").Write(eventVariableName).WriteLine(");"); - - if (methodTarget.Baggage.Count > 0) + writer.IfBlock( + activityVariableName + " != null", + body => { - writer.NewLine(); + var exceptionParam = + methodTarget.Parameters.FirstOrDefault(m => m.IsException) + ?? methodTarget.Tags.FirstOrDefault(m => m.IsException); + var tagsParameterName = EmitEventTags( + writer, + methodTarget, + activityVariableName, + tagsParam, + escapeParam + ); + + var eventVariableName = "activityEvent" + methodTarget.MethodName; + + writer + .NewLine() + .Write(TypeLibrary.Activities.SystemDiagnostics.ActivityEvent) + .Write(' ') + .Write(eventVariableName) + .Write(" = new ") + // Use explicit type for C# 7.3 compatibility (target-typed new() requires C# 9+) + .Write(TypeLibrary.Activities.SystemDiagnostics.ActivityEvent) + .Write("(name: ") + .Write(methodTarget.ActivityOrEventName.Wrap()) + // timestamp: + .Write(", timestamp: ") + .Write(timestampParam?.ParameterName ?? "default") + // tags: + .Write(", tags: ") + .Write(tagsParameterName) + .Line(");"); + + writer.NewLine().Write(activityVariableName).Write(".AddEvent(").Write(eventVariableName).Line(");"); + + if (methodTarget.Baggage.Count > 0) + { + writer.NewLine(); - EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, output); - } + EmitTagsOrBaggageParameters(writer, activityVariableName, false, methodTarget, false, output); + } - EmitSetStatus(writer, methodTarget, activityVariableName, statusDescriptionParam, exceptionParam); - } + EmitSetStatus(writer, methodTarget, activityVariableName, statusDescriptionParam, exceptionParam); + } + ); context.CancellationToken.ThrowIfCancellationRequested(); @@ -131,7 +138,7 @@ static string EmitEventTags( if (tagsParam != null) writer.Write(tagsParam.ParameterName); - writer.WriteLine(");"); + writer.Line(");"); var useRecordedExceptionRules = methodTarget.EventAttribute?.UseRecordExceptionRules @@ -151,12 +158,12 @@ void EmitTag() { if (methodTarget.ActivityOrEventName == PropertyLibrary.Activities.Tag_ExceptionEventName) { - writer.Write("if (").Write(tagParam.ParameterName).WriteLine(" != null)"); - using (writer.OpenBlockScope()) - { - // We want the details inside of the current event. - EmitExceptionParam(writer, tagsListVariableName, escapeValue, tagParam.ParameterName); - } + writer.IfBlock( + tagParam.ParameterName + " != null", + body => + // We want the details inside of the current event. + EmitExceptionParam(writer, tagsListVariableName, escapeValue, tagParam.ParameterName) + ); } else { @@ -171,17 +178,16 @@ void EmitTag() .Write(tagParam.ParameterName) .Write(", escape: ") .Write(escapeValue) - .WriteLine(");"); + .Line(");"); } else { - writer - .Write(tagsListVariableName) - .Write(".Add(") - .Write(tagParam.GeneratedName.Wrap()) - .Write(", ") - .Write(tagParam.ParameterName) - .WriteLine(".ToString());"); + writer.MethodCallOn( + tagsListVariableName, + "Add", + tagParam.GeneratedName.Wrap(), + tagParam.ParameterName + ".ToString()" + ); } } } @@ -193,15 +199,13 @@ void EmitTag() .Write(tagParam.GeneratedName.Wrap()) .Write(", ") .Write(tagParam.ParameterName) - .WriteLine(");"); + .Line(");"); } } if (tagParam.SkipOnNullOrEmpty) { - writer.Write("if (").Write(tagParam.ParameterName).WriteLine(" != default)"); - using (writer.OpenBlockScope()) - EmitTag(); + writer.IfBlock(tagParam.ParameterName + " != default", _ => EmitTag()); } else { @@ -247,6 +251,6 @@ static void EmitSetStatus( } } - writer.WriteLine(");"); + writer.Line(");"); } } diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Fields.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Fields.cs index e0b97dc8..a33399a7 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Fields.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Fields.cs @@ -20,15 +20,19 @@ static void EmitFields(ActivityOutputContext output, CodeWriter writer, SourcePr } writer - .Write("readonly static ") - .Write(TypeLibrary.Activities.SystemDiagnostics.ActivitySource) - .Write(' ') - .Write(PropertyLibrary.Activities.ActivitySourceFieldName) - .Write(" = new ") - .Write(TypeLibrary.Activities.SystemDiagnostics.ActivitySource) - .Write('(') - .Write(activitySourceName!.Wrap()) - .WriteLine(");") + .Field( + new FieldDeclarationOptions( + PropertyLibrary.Activities.ActivitySourceFieldName, + TypeLibrary.Activities.SystemDiagnostics.ActivitySource.AsTypeReference() + ) + { + IsStatic = true, + IsReadOnly = true, + Initializer = + $"new {(string)TypeLibrary.Activities.SystemDiagnostics.ActivitySource}({activitySourceName!.Wrap()})", + IncludeGeneratedAttributes = false, + } + ) .NewLine(); } } diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Methods.cs index 45134e33..2e83ee00 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Methods.cs @@ -45,33 +45,38 @@ SourceProductionContext context output.Context.Debug($"Generating {PropertyLibrary.Activities.RecordExceptionMethodName}."); - writer - .Write("static void ") - .Write(PropertyLibrary.Activities.RecordExceptionMethodName) - .Write('(') - .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()) + using ( + writer.MethodScope( + new MethodDeclarationOptions( + PropertyLibrary.Activities.RecordExceptionMethodName, + PurviewTypeLibrary.System.Void.AsTypeReference() + ) + { + IsStatic = true, + Parameters = + [ + new ParameterDeclarationOptions( + "activity", + TypeLibrary.Activities.SystemDiagnostics.Activity.MakeNullable(writer) + ), + new ParameterDeclarationOptions("exception", TypeLibrary.System.Exception.MakeNullable(writer)), + new ParameterDeclarationOptions("escape", PurviewTypeLibrary.System.Boolean.AsTypeReference()), + ], + IncludeGeneratedAttributes = false, + } + ) + ) { - writer.WriteLine("if (activity == null || exception == null)"); - using (writer.OpenBlockScope()) - writer.WriteLine("return;"); + writer.IfBlock("activity == null || exception == null", static body => body.Return()); writer.NewLine(); const string tagsListVariableName = "tagsCollection"; - writer - .Write(TypeLibrary.Activities.SystemDiagnostics.ActivityTagsCollection) - .Write(' ') - .Write(tagsListVariableName) - .Write(" = new ") - .Write(TypeLibrary.Activities.SystemDiagnostics.ActivityTagsCollection) - .WriteLine("();"); + writer.Assignment( + TypeLibrary.Activities.SystemDiagnostics.ActivityTagsCollection, + tagsListVariableName, + "new " + TypeLibrary.Activities.SystemDiagnostics.ActivityTagsCollection + "()" + ); EmitExceptionParam(writer, tagsListVariableName, "escape", "exception"); @@ -92,9 +97,9 @@ SourceProductionContext context // tags: .Write(", tags: ") .Write(tagsListVariableName) - .WriteLine(");"); + .Line(");"); - writer.NewLine().Write("activity.AddEvent(").Write(eventVariableName).WriteLine(");"); + writer.NewLine().Write("activity.AddEvent(").Write(eventVariableName).Line(");"); } writer.NewLine(); @@ -113,7 +118,7 @@ string exceptionParam .Write(PropertyLibrary.Activities.Tag_ExceptionEscaped.Wrap()) .Write(", ") .Write(escapeParam) - .WriteLine(");"); + .Line(");"); writer .Write(tagsListVariableName) @@ -121,15 +126,14 @@ string exceptionParam .Write(PropertyLibrary.Activities.Tag_ExceptionMessage.Wrap()) .Write(", ") .Write(exceptionParam) - .WriteLine(".Message);"); + .Line(".Message);"); - writer - .Write(tagsListVariableName) - .Write(".Add(") - .Write(PropertyLibrary.Activities.Tag_ExceptionType.Wrap()) - .Write(", ") - .Write(exceptionParam) - .WriteLine(".GetType().FullName);"); + writer.MethodCallOn( + tagsListVariableName, + "Add", + PropertyLibrary.Activities.Tag_ExceptionType.Wrap(), + exceptionParam + ".GetType().FullName" + ); writer .Write(tagsListVariableName) @@ -137,40 +141,39 @@ string exceptionParam .Write(PropertyLibrary.Activities.Tag_ExceptionStackTrace.Wrap()) .Write(", ") .Write(exceptionParam) - .WriteLine(".StackTrace);"); + .Line(".StackTrace);"); } static void EmitThrowStub(CodeWriter writer, ActivityBasedGenerationTarget methodTarget) { - writer.NewLine().Write("public ").Write(methodTarget.ReturnType); - - writer.Write(' ').Write(methodTarget.MethodName); - - if (methodTarget.TypeParameters.Count > 0) - { - writer.Write('<'); - for (var i = 0; i < methodTarget.TypeParameters.Count; i++) - { - if (i > 0) - writer.Write(", "); - writer.Write(methodTarget.TypeParameters[i]); - } - writer.Write('>'); - } - - writer.Write('('); + writer.NewLine(); - for (var i = 0; i < methodTarget.Parameters.Count; i++) + using ( + writer.MethodScope( + new MethodDeclarationOptions( + methodTarget.MethodName, + methodTarget.ReturnType, + TypeDeclarationAccessibility.Public + ) + { + Parameters = + [ + .. methodTarget.Parameters.Select(p => new ParameterDeclarationOptions( + p.ParameterName, + p.ParameterType + )), + ], + GenericTypes = [.. methodTarget.TypeParameters], + ExpressionBody = "throw new global::System.NotSupportedException()", + IncludeGeneratedAttributes = false, + } + ) + ) { - if (i > 0) - writer.Write(", "); - writer - .Write(methodTarget.Parameters[i].ParameterType) - .Write(' ') - .Write(methodTarget.Parameters[i].ParameterName); + // } - writer.WriteLine(") => throw new global::System.NotSupportedException();").NewLine(); + writer.NewLine(); } static void EmitMethod( @@ -233,7 +236,7 @@ SourceProductionContext context context.CancellationToken.ThrowIfCancellationRequested(); using ( - writer.WriteMethodScope( + writer.MethodScope( new MethodDeclarationOptions( privateMethodName, methodTarget.ReturnType, @@ -277,7 +280,7 @@ SourceProductionContext context writer.NewLine(); using ( - writer.WriteMethodScope( + writer.MethodScope( new MethodDeclarationOptions( methodTarget.MethodName, methodTarget.ReturnType, @@ -325,24 +328,24 @@ SourceProductionContext context .Write(methodTarget.MethodName) .Write("_Activity(") .Write(paramList) - .WriteLine(");"); + .Line(");"); } else { - writer.Write(methodTarget.MethodName).Write("_Activity(").Write(paramList).WriteLine(");"); + writer.Write(methodTarget.MethodName).Write("_Activity(").Write(paramList).Line(");"); } } // Call Logging private method if (methodTargets.HasFlag(GenerationType.Logging)) { - writer.Write(methodTarget.MethodName).Write("_Logging(").Write(loggingMetricsParamList).WriteLine(");"); + writer.Write(methodTarget.MethodName).Write("_Logging(").Write(loggingMetricsParamList).Line(");"); } // Call Metrics private method if (methodTargets.HasFlag(GenerationType.Metrics)) { - writer.Write(methodTarget.MethodName).Write("_Metrics(").Write(loggingMetricsParamList).WriteLine(");"); + writer.Write(methodTarget.MethodName).Write("_Metrics(").Write(loggingMetricsParamList).Line(");"); } // Return result if applicable @@ -365,7 +368,7 @@ SourceProductionContext context ) { using ( - writer.WriteMethodScope( + writer.MethodScope( new(methodTarget.MethodName, methodTarget.ReturnType, TypeDeclarationAccessibility.Public) { Parameters = diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Parameters.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Parameters.cs index d727b80b..ad937fa0 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Parameters.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.Parameters.cs @@ -44,7 +44,7 @@ void EmitParameter(ActivityBasedParameterTarget param) writer.Write(".ToString()"); } - writer.WriteLine(");"); + writer.Line(");"); } void EmitParameters() @@ -56,9 +56,7 @@ void EmitParameters() if (param.SkipOnNullOrEmpty) { - writer.Write("if (").Write(param.ParameterName).WriteLine(" != default)"); - using (writer.OpenBlockScope()) - EmitParameter(param); + writer.IfBlock(param.ParameterName + " != default", _ => EmitParameter(param)); } else { @@ -69,9 +67,7 @@ void EmitParameters() if (checkForNullableActivity) { - writer.NewLine().Write("if (").Write(activityVariableName).WriteLine(" != null)"); - using (writer.OpenBlockScope()) - EmitParameters(); + writer.NewLine().IfBlock(activityVariableName + " != null", _ => EmitParameters()); } else { diff --git a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.cs b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.cs index 7d5ebdab..80994982 100644 --- a/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/ActivitySourceTargetClassEmitter.cs @@ -13,7 +13,7 @@ public static void GenerateImplementation(ActivityOutputContext output, SourcePr output.Context.Debug($"Generating activity class for: {target.FullyQualifiedName}"); var writer = output.CreateWriter(); - using (writer.WriteBlockNamespaceScope(target.ClassNamespace)) + using (writer.BlockNamespaceScope(target.ClassNamespace)) { List parentScopes = []; if (target.TelemetryGeneration.TelemetryNamesNamespace == null) @@ -21,7 +21,7 @@ public static void GenerateImplementation(ActivityOutputContext output, SourcePr foreach (var parent in target.ParentClasses) { parentScopes.Add( - writer.WriteClassScope(new(parent) { IsSealed = false, IncludeGeneratedAttributes = false }) + writer.ClassScope(new(parent) { IsSealed = false, IncludeGeneratedAttributes = false }) ); } } diff --git a/src/src/SourceGenerator/Emitters/ConstructorEmitter.cs b/src/src/SourceGenerator/Emitters/ConstructorEmitter.cs index 847b77e5..214f98d8 100644 --- a/src/src/SourceGenerator/Emitters/ConstructorEmitter.cs +++ b/src/src/SourceGenerator/Emitters/ConstructorEmitter.cs @@ -30,7 +30,7 @@ GenerationContext generationContext } writer.NewLine(); - writer.WriteConstructor( + writer.Constructor( new ConstructorDeclarationOptions(classNameToGenerate, TypeDeclarationAccessibility.Public) { Parameters = BuildParameters(generationType, interfaceType, generationContext), diff --git a/src/src/SourceGenerator/Emitters/DependencyInjectionClassEmitter.cs b/src/src/SourceGenerator/Emitters/DependencyInjectionClassEmitter.cs index 1f92fa7a..e0e8b41d 100644 --- a/src/src/SourceGenerator/Emitters/DependencyInjectionClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/DependencyInjectionClassEmitter.cs @@ -49,16 +49,16 @@ GenerationContext generationContext // 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); + writer.Using(PropertyLibrary.DependencyInjection.DependencyInjectionNamespace); using ( - writer.WriteBlockNamespaceScope( + writer.BlockNamespaceScope( attribute.TelemetryNamesNamespace ?? PropertyLibrary.DependencyInjection.DependencyInjectionNamespace ) ) { using ( - writer.WriteClassScope( + writer.ClassScope( new(classNameToGenerate!, classAccessibility) { IsStatic = true, @@ -102,8 +102,14 @@ CancellationToken token generationContext.Debug($"Emitting DI method for {interfaceName}."); + writer.XmlSummary( + $"Registers the generated {XmlSee("global::" + interfaceType.RenderFullName)} implementation with the service collection." + ); + writer.XmlParam("services", "The service collection to register the telemetry implementation with."); + writer.XmlReturn("The service collection, for chaining."); + using ( - writer.WriteMethodScope( + writer.MethodScope( new MethodDeclarationOptions( "Add" + methodName, TypeLibrary.DependencyInjection.IServiceCollection, @@ -123,13 +129,14 @@ CancellationToken token ) ) { - writer - .Write("return services.AddSingleton<") - .Write(interfaceType.RenderFullName) - .Write(", ") - .Write("global::") - .Write(BuildImplQualifiedName(telemetryNamesNamespace, interfaceType, className)) - .WriteLine(">();"); + writer.Return( + "services.AddSingleton<" + + interfaceType.RenderFullName + + ", " + + "global::" + + BuildImplQualifiedName(telemetryNamesNamespace, interfaceType, className) + + ">()" + ); } } diff --git a/src/src/SourceGenerator/Emitters/EmitterHelpers.cs b/src/src/SourceGenerator/Emitters/EmitterHelpers.cs index 0c9eb888..7eba1d59 100644 --- a/src/src/SourceGenerator/Emitters/EmitterHelpers.cs +++ b/src/src/SourceGenerator/Emitters/EmitterHelpers.cs @@ -43,7 +43,7 @@ TypeDeclarationAccessibility accessibility if (SharedHelpers.ShouldEmitClassAttributes(requestingType, generationType)) attributes = [EditorBrowsableAttribute()]; - return writer.WriteClassScope( + return writer.ClassScope( new TypeDeclarationOptions(className, accessibility) { IncludeGeneratedAttributes = false, diff --git a/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs b/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs index 3469e521..ee35661d 100644 --- a/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs +++ b/src/src/SourceGenerator/Emitters/GeneratedTypesEmitter.cs @@ -11,55 +11,157 @@ namespace Purview.Telemetry.SourceGenerator.Emitters; /// static class GeneratedTypesEmitter { - static readonly Dictionary> Emitters = new() + static IEnumerable<(TypeIdentity Type, Action Emitter)> GetEmitters() { // Telemetry Shared - [TypeLibrary.TelemetryShared.TagAttribute] = WriteTagLikeAttribute, - [TypeLibrary.TelemetryShared.ExcludeAttribute] = (writer, type) => - WriteSimpleAttribute(writer, type, AttributeTargets.Method, includeSuppressMessage: false), - [TypeLibrary.TelemetryShared.TelemetryGenerationAttribute] = WriteTelemetryGenerationAttribute, - [TypeLibrary.TelemetryShared.Targets] = WriteTargetsEnum, - [TypeLibrary.TelemetryShared.NamingConvention] = WriteNamingConventionEnum, - [TypeLibrary.TelemetryShared.ExcludeTargetsAttribute] = WriteExcludeTargetsAttribute, + yield return ( + TypeLibrary.TelemetryShared.TagAttribute, + (writer, type) => + WriteTagLikeAttribute(writer, type, "Marks a parameter as a tag for an activity, event or instrument.") + ); + + yield return ( + TypeLibrary.TelemetryShared.ExcludeAttribute, + (writer, type) => + WriteSimpleAttribute( + writer, + type, + AttributeTargets.Method, + includeSuppressMessage: false, + summary: "Marks a method to be excluded from telemetry generation." + ) + ); + yield return (TypeLibrary.TelemetryShared.TelemetryGenerationAttribute, WriteTelemetryGenerationAttribute); + yield return (TypeLibrary.TelemetryShared.Targets, WriteTargetsEnum); + yield return (TypeLibrary.TelemetryShared.NamingConvention, WriteNamingConventionEnum); + yield return (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), - [TypeLibrary.Activities.EscapeAttribute] = (writer, type) => - WriteSimpleAttribute(writer, type, AttributeTargets.Parameter, includeSuppressMessage: false), - [TypeLibrary.Activities.StatusDescriptionAttribute] = (writer, type) => - WriteSimpleAttribute(writer, type, AttributeTargets.Parameter, includeSuppressMessage: false), + yield return ( + TypeLibrary.Activities.BaggageAttribute, + (writer, type) => + WriteTagLikeAttribute(writer, type, "Marks a parameter as baggage to be attached to an activity.") + ); + yield return (TypeLibrary.Activities.ActivitySourceGenerationAttribute, WriteActivitySourceGenerationAttribute); + yield return (TypeLibrary.Activities.ActivitySourceAttribute, WriteActivitySourceAttribute); + yield return (TypeLibrary.Activities.ActivityAttribute, WriteActivityAttribute); + yield return (TypeLibrary.Activities.EventAttribute, WriteEventAttribute); + yield return ( + TypeLibrary.Activities.ContextAttribute, + (writer, type) => + WriteSimpleAttribute( + writer, + type, + AttributeTargets.Method, + includeSuppressMessage: false, + summary: "Marks a parameter as an activity context." + ) + ); + yield return ( + TypeLibrary.Activities.EscapeAttribute, + (writer, type) => + WriteSimpleAttribute( + writer, + type, + AttributeTargets.Parameter, + includeSuppressMessage: false, + summary: "Marks a parameter as the escape flag for a recorded exception." + ) + ); + yield return ( + TypeLibrary.Activities.StatusDescriptionAttribute, + (writer, type) => + WriteSimpleAttribute( + writer, + type, + AttributeTargets.Parameter, + includeSuppressMessage: false, + summary: "Marks a parameter as the status description of an activity or event." + ) + ); // 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, + yield return (TypeLibrary.Logging.LoggerGenerationAttribute, WriteLoggerGenerationAttribute); + yield return (TypeLibrary.Logging.LoggerAttribute, WriteLoggerAttribute); + yield return (TypeLibrary.Logging.LogAttribute, WriteLogAttribute); + yield return (TypeLibrary.Logging.LogPrefixType, WriteLogPrefixTypeEnum); + yield return (TypeLibrary.Logging.LoggerGenerationMode, WriteLoggerGenerationModeEnum); + yield return (TypeLibrary.Logging.ExpandEnumerableAttribute, WriteExpandEnumerableAttribute); + yield return ( + TypeLibrary.Logging.TraceAttribute, + (writer, type) => WriteSpecificLogAttribute(writer, type, "Marks a method as a trace-level log method.") + ); + yield return ( + TypeLibrary.Logging.DebugAttribute, + (writer, type) => WriteSpecificLogAttribute(writer, type, "Marks a method as a debug-level log method.") + ); + yield return ( + TypeLibrary.Logging.InfoAttribute, + (writer, type) => WriteSpecificLogAttribute(writer, type, "Marks a method as an informational log method.") + ); + yield return ( + TypeLibrary.Logging.WarningAttribute, + (writer, type) => WriteSpecificLogAttribute(writer, type, "Marks a method as a warning-level log method.") + ); + yield return ( + TypeLibrary.Logging.ErrorAttribute, + (writer, type) => WriteSpecificLogAttribute(writer, type, "Marks a method as an error-level log method.") + ); + yield return ( + TypeLibrary.Logging.CriticalAttribute, + (writer, type) => WriteSpecificLogAttribute(writer, type, "Marks a method as a critical-level log method.") + ); // 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), - [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, - }; + yield return (TypeLibrary.Metrics.MeterGenerationAttribute, WriteMeterGenerationAttribute); + yield return (TypeLibrary.Metrics.MeterAttribute, WriteMeterAttribute); + yield return (TypeLibrary.Metrics.MeterNameGenerationType, WriteMeterNameGenerationTypeEnum); + yield return ( + TypeLibrary.Metrics.InstrumentMeasurementAttribute, + (writer, type) => + WriteSimpleAttribute( + writer, + type, + AttributeTargets.Parameter, + includeSuppressMessage: false, + summary: "Marks a parameter as the measurement value of an instrument." + ) + ); + yield return ( + TypeLibrary.Metrics.AutoCounterAttribute, + (writer, type) => + WriteAutoCounterAttribute(writer, type, "Marks a method as an auto-incrementing counter instrument.") + ); + yield return ( + TypeLibrary.Metrics.CounterAttribute, + (writer, type) => WriteCounterLikeAttribute(writer, type, "Marks a method as a counter instrument.") + ); + yield return ( + TypeLibrary.Metrics.UpDownCounterAttribute, + (writer, type) => + WriteCounterLikeAttribute(writer, type, "Marks a method as an up-down counter instrument.") + ); + yield return ( + TypeLibrary.Metrics.HistogramAttribute, + (writer, type) => WriteCounterLikeAttribute(writer, type, "Marks a method as a histogram instrument.") + ); + yield return ( + TypeLibrary.Metrics.ObservableCounterAttribute, + (writer, type) => + WriteObservableCounterLikeAttribute(writer, type, "Marks a method as an observable counter instrument.") + ); + yield return ( + TypeLibrary.Metrics.ObservableUpDownCounterAttribute, + (writer, type) => + WriteObservableCounterLikeAttribute( + writer, + type, + "Marks a method as an observable up-down counter instrument." + ) + ); + yield return ( + TypeLibrary.Metrics.ObservableGaugeAttribute, + (writer, type) => + WriteObservableCounterLikeAttribute(writer, type, "Marks a method as an observable gauge instrument.") + ); + } public static void EmitAll(IncrementalGeneratorPostInitializationContext context) { @@ -69,14 +171,14 @@ public static void EmitAll(IncrementalGeneratorPostInitializationContext context context.AddEmbeddedAttributeDefinition(); var settings = GenerationSettings.Create(); - foreach (var type in TypeLibrary.GetAllGeneratedTypes()) + foreach (var emitter in GetEmitters()) { CodeWriter writer = new(settings); WriteMarkerFileHeader(writer); - Emit(writer, type); + emitter.Emitter(writer, emitter.Type); - context.AddSource($"{type.MetadataFullName}.g.cs", writer); + context.AddSource($"{emitter.Type.MetadataFullName}.g.cs", writer); } } @@ -88,22 +190,13 @@ public static void EmitAll(IncrementalGeneratorPostInitializationContext context /// static void WriteMarkerFileHeader(CodeWriter writer) { - writer.WriteAutoGeneratedHeader(nullableDirective: NullableDirectiveMode.Disable); + writer.AutoGeneratedHeader(nullableDirective: NullableDirectiveMode.Disable); writer - .WriteLine("#if !NET48_OR_GREATER && !PURVIEW_TELEMETRY_NON_NULLABLE") - .WriteLine("#nullable enable") - .WriteLine("#endif") - .NewLine() - .WriteLine("#pragma warning disable CS8625") - .NewLine(); - } - - static void Emit(CodeWriter writer, TypeIdentity type) - { - if (!Emitters.TryGetValue(type, out var emit)) - throw new ArgumentOutOfRangeException(nameof(type), type.Name, "Unknown generation type requested."); - - emit(writer, type); + .HashDefines( + "!NET48_OR_GREATER && !PURVIEW_TELEMETRY_NON_NULLABLE", + hashWriter => hashWriter.Line("#nullable enable") + ) + .PragmaDisable("CS8625"); } // ------------------------------------------------------------------------------------------- @@ -117,106 +210,131 @@ static void EmitAttribute( AttributeTargets targets, Action body, bool wrapInExcludeLoggingGuard = false, - bool includeSuppressMessage = true + bool includeSuppressMessage = true, + string? summary = null ) { - if (wrapInExcludeLoggingGuard) - writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); + using var scope = wrapInExcludeLoggingGuard + ? writer.HashDefinesScope("!EXCLUDE_PURVIEW_TELEMETRY_LOGGING") + : writer.EmptyScope(); var attributes = ImmutableArray.Empty; attributes = attributes.Add(ConditionalAttribute()); if (includeSuppressMessage) attributes = attributes.Add(SuppressMessageAttribute()); - writer - .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) - .WriteAttributeClass( - new(type.Name, TypeDeclarationAccessibility.Internal) { Attributes = attributes }, - targets, - body - ); - - if (wrapInExcludeLoggingGuard) - writer.WriteLine("#endif"); + writer.FileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace); + if (summary != null) + writer.XmlSummary(summary); + writer.AttributeClass( + new(type.Name, TypeDeclarationAccessibility.Internal) { Attributes = attributes }, + targets, + body + ); } static void WriteSimpleAttribute( CodeWriter writer, TypeIdentity type, AttributeTargets targets, - bool includeSuppressMessage + bool includeSuppressMessage, + string? summary = null ) { - EmitAttribute(writer, type, targets, static _ => { }, includeSuppressMessage: includeSuppressMessage); + EmitAttribute( + writer, + type, + targets, + static _ => { }, + includeSuppressMessage: includeSuppressMessage, + summary: summary + ); } static AttributeDeclarationOptions ConditionalAttribute() => new(new TypeIdentity("ConditionalAttribute", "System.Diagnostics")) { - Arguments = [new("\"PURVIEW_TELEMETRY_ATTRIBUTES\"")], + Arguments = [new("PURVIEW_TELEMETRY_ATTRIBUTES".Surround())], }; static AttributeDeclarationOptions SuppressMessageAttribute() => new(new TypeIdentity("SuppressMessageAttribute", "System.Diagnostics.CodeAnalysis")) { - Arguments = [new("\"Design\""), new("\"CA1019:Define accessors for attribute arguments\"")], + Arguments = [new("Design".Surround()), new("CA1019:Define accessors for attribute arguments".Surround())], }; // ------------------------------------------------------------------------------------------- // Members // ------------------------------------------------------------------------------------------- - static void WriteEmptyConstructor(CodeWriter writer, TypeIdentity type) => - writer.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public), - static _ => { } - ); + static void WriteEmptyConstructor(CodeWriter writer, TypeIdentity type, string? summary = null) + { + if (summary != null) + writer.XmlSummary(summary); + writer.Constructor(new(type.Name, TypeDeclarationAccessibility.Public), static _ => { }); + } - static void WriteNameConstructor(CodeWriter writer, TypeIdentity type) => - writer.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + static void WriteNameConstructor(CodeWriter writer, TypeIdentity type, string? summary = null) + { + if (summary != null) + { + writer.XmlSummary(summary); + writer.XmlParam("name", "The name of the telemetry entry."); + } + writer.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = - [ - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), - ], + Parameters = [new("name", PurviewTypeLibrary.System.String.AsTypeReference())], }, - ctor => ctor.WriteAssignment("Name", "name") + ctor => ctor.Assignment("Name", "name") ); + } - static void WriteMessageTemplateConstructor(CodeWriter writer, TypeIdentity type) => - writer.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + static void WriteMessageTemplateConstructor(CodeWriter writer, TypeIdentity type, string? summary = null) + { + if (summary != null) + { + writer.XmlSummary(summary); + writer.XmlParam("messageTemplate", "The message template used to generate the log message."); + } + writer.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = - [ - new ParameterDeclarationOptions( - "messageTemplate", - PurviewTypeLibrary.System.String.AsTypeReference() - ), - ], + Parameters = [new("messageTemplate", PurviewTypeLibrary.System.String.AsTypeReference())], }, - ctor => ctor.WriteAssignment("MessageTemplate", "messageTemplate") + ctor => ctor.Assignment("MessageTemplate", "messageTemplate") ); + } - static void WriteEventIdConstructor(CodeWriter writer, TypeIdentity type) => - writer.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + static void WriteEventIdConstructor(CodeWriter writer, TypeIdentity type, string? summary = null) + { + if (summary != null) + { + writer.XmlSummary(summary); + writer.XmlParam("eventId", "The event identifier of the log entry."); + } + writer.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [new("eventId", PurviewTypeLibrary.System.Int32.AsTypeReference())], }, - ctor => ctor.WriteAssignment("EventId", "eventId") + ctor => ctor.Assignment("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) + static void WritePublicProperty( + CodeWriter writer, + string name, + TypeReference type, + string? initializer = null, + string? summary = null + ) { - writer.WriteProperty( - new PropertyDeclarationOptions(name, type, TypeDeclarationAccessibility.Public) - { - HasSetter = true, - Initializer = initializer, - } + if (summary != null) + writer.XmlSummary(summary); + writer.Property( + new(name, type, TypeDeclarationAccessibility.Public) { HasSetter = true, Initializer = initializer } ); } @@ -224,40 +342,54 @@ static void WritePublicProperty(CodeWriter writer, string name, TypeReference ty /// 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) + static void WriteNullableStringProperty(CodeWriter writer, string name, string? summary = null) { - writer.WriteLine("#if NET48_OR_GREATER || PURVIEW_TELEMETRY_NON_NULLABLE"); - writer.WriteProperty( - new PropertyDeclarationOptions( - name, - PurviewTypeLibrary.System.String.AsTypeReference(), - TypeDeclarationAccessibility.Public - ) - { - HasSetter = true, - IncludeGeneratedAttributes = false, - } - ); - writer.WriteLine("#else"); - writer.WriteProperty( - new(name, PurviewTypeLibrary.System.String.MakeNullable(writer), TypeDeclarationAccessibility.Public) + writer.HashDefines( + "NET48_OR_GREATER || PURVIEW_TELEMETRY_NON_NULLABLE", + hashWriter => { - HasSetter = true, - IncludeGeneratedAttributes = false, + if (summary != null) + hashWriter.XmlSummary(summary); + hashWriter + .Property( + new( + name, + PurviewTypeLibrary.System.String.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) + { + HasSetter = true, + IncludeGeneratedAttributes = false, + } + ) + .HashElse() + .Property( + new( + name, + PurviewTypeLibrary.System.String.MakeNullable(writer), + 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) + static void WritePlainStringProperty( + CodeWriter writer, + string name, + string? initializer = null, + string? summary = null + ) { - writer.WriteProperty( - new PropertyDeclarationOptions( - name, - PurviewTypeLibrary.System.String.AsTypeReference(), - TypeDeclarationAccessibility.Public - ) + if (summary != null) + writer.XmlSummary(summary); + writer.Property( + new(name, PurviewTypeLibrary.System.String.AsTypeReference(), TypeDeclarationAccessibility.Public) { HasSetter = true, IncludeGeneratedAttributes = false, @@ -270,7 +402,7 @@ static void WritePlainStringProperty(CodeWriter writer, string name, string? ini // Shared templates // ------------------------------------------------------------------------------------------- - static void WriteTagLikeAttribute(CodeWriter writer, TypeIdentity type) + static void WriteTagLikeAttribute(CodeWriter writer, TypeIdentity type, string? summary = null) { EmitAttribute( writer, @@ -278,30 +410,26 @@ static void WriteTagLikeAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Parameter, body => { - WriteEmptyConstructor(body, type); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = - [ - new ParameterDeclarationOptions( - "skipOnNullOrEmpty", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ), - ], + Parameters = [new("skipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean.AsTypeReference())], }, - ctor => ctor.WriteAssignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty") + ctor => ctor.Assignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty") + ); + body.XmlSummary( + $"Constructs a new instance specifying the {XmlSee("Name")} and whether empty values are skipped." ); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlParam("name", $"The {XmlSee("Name")}."); + body.XmlParam("skipOnNullOrEmpty", "Whether to skip the value when it is null or empty."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), - new ParameterDeclarationOptions( - "skipOnNullOrEmpty", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ) + new("name", PurviewTypeLibrary.System.String.AsTypeReference()), + new("skipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "false", }, @@ -309,14 +437,20 @@ static void WriteTagLikeAttribute(CodeWriter writer, TypeIdentity type) }, ctor => { - ctor.WriteAssignment("Name", "name"); - ctor.WriteAssignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty"); + ctor.Assignment("Name", "name"); + ctor.Assignment("SkipOnNullOrEmpty", "skipOnNullOrEmpty"); } ); - WriteNullableStringProperty(body, "Name"); - WritePublicProperty(body, "SkipOnNullOrEmpty", PurviewTypeLibrary.System.Boolean.AsTypeReference()); - } + WriteNullableStringProperty(body, "Name", "Optional. Gets the name of the tag or baggage value."); + WritePublicProperty( + body, + "SkipOnNullOrEmpty", + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + summary: "Determines whether the value is skipped when it is null or empty." + ); + }, + summary: summary ); } @@ -330,27 +464,24 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty AttributeTargets.Assembly | AttributeTargets.Interface, body => { - WriteEmptyConstructor(body, type); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + body.XmlSummary( + "Constructs a new instance specifying whether a dependency-injection extension is generated, the generated class name and the dependency-injection class name." + ); + body.XmlParam("generateDependencyExtension", "Whether to generate a dependency-injection extension."); + body.XmlParam("className", "The name of the generated telemetry class."); + body.XmlParam("dependencyInjectionClassName", "The name of the generated dependency-injection class."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions( - "generateDependencyExtension", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ), - new ParameterDeclarationOptions( - "className", - PurviewTypeLibrary.System.String.AsTypeReference() - ) + new("generateDependencyExtension", PurviewTypeLibrary.System.Boolean.AsTypeReference()), + new("className", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, - new ParameterDeclarationOptions( - "dependencyInjectionClassName", - PurviewTypeLibrary.System.String.AsTypeReference() - ) + new("dependencyInjectionClassName", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, @@ -358,24 +489,23 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty }, ctor => { - ctor.WriteAssignment("GenerateDependencyExtension", "generateDependencyExtension"); - ctor.WriteAssignment("ClassName", "className"); - ctor.WriteAssignment("DependencyInjectionClassName", "dependencyInjectionClassName"); + ctor.Assignment("GenerateDependencyExtension", "generateDependencyExtension"); + ctor.Assignment("ClassName", "className"); + ctor.Assignment("DependencyInjectionClassName", "dependencyInjectionClassName"); } ); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlSummary( + "Constructs a new instance specifying the generated class name and the dependency-injection class name." + ); + body.XmlParam("className", "The name of the generated telemetry class."); + body.XmlParam("dependencyInjectionClassName", "The name of the generated dependency-injection class."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions( - "className", - PurviewTypeLibrary.System.String.AsTypeReference() - ), - new ParameterDeclarationOptions( - "dependencyInjectionClassName", - PurviewTypeLibrary.System.String.AsTypeReference() - ) + new("className", PurviewTypeLibrary.System.String.AsTypeReference()), + new("dependencyInjectionClassName", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, @@ -383,8 +513,8 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty }, ctor => { - ctor.WriteAssignment("ClassName", "className"); - ctor.WriteAssignment("DependencyInjectionClassName", "dependencyInjectionClassName"); + ctor.Assignment("ClassName", "className"); + ctor.Assignment("DependencyInjectionClassName", "dependencyInjectionClassName"); } ); @@ -392,30 +522,47 @@ static void WriteTelemetryGenerationAttribute(CodeWriter writer, TypeIdentity ty body, "GenerateDependencyExtension", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether a dependency-injection extension method is generated." + ); + WriteNullableStringProperty(body, "ClassName", "The name of the generated telemetry class."); + WriteNullableStringProperty( + body, + "DependencyInjectionClassName", + "The name of the generated dependency-injection class." ); - WriteNullableStringProperty(body, "ClassName"); - WriteNullableStringProperty(body, "DependencyInjectionClassName"); WritePublicProperty( body, "DependencyInjectionClassIsPublic", - PurviewTypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + summary: "Determines whether the dependency-injection class is generated as public." ); WritePublicProperty( body, "NamingConvention", namingConvention.AsTypeReference(), - $"{namingConvention.RenderFullName}.OpenTelemetry" + $"{namingConvention.RenderFullName}.OpenTelemetry", + "Determines the naming convention used for generated telemetry names." ); WritePublicProperty( body, "GenerateTelemetryNamesClass", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether a telemetry names class is generated." ); - WriteNullableStringProperty(body, "TelemetryNamesClassName"); - WriteNullableStringProperty(body, "TelemetryNamesNamespace"); - } + WriteNullableStringProperty( + body, + "TelemetryNamesClassName", + "The name of the generated telemetry names class." + ); + WriteNullableStringProperty( + body, + "TelemetryNamesNamespace", + "The namespace of the generated telemetry names class." + ); + }, + summary: "Specifies the telemetry generation behaviour for an interface or assembly." ); } @@ -430,17 +577,19 @@ static void WriteExcludeTargetsAttribute(CodeWriter writer, TypeIdentity type) body => { body.XmlSummary("Constructs a new instance with the specified targets to exclude."); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlParam("targets", $"The {XmlSee("ExcludedTargets")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = [new ParameterDeclarationOptions("targets", targets.AsTypeReference())], + Parameters = [new("targets", targets.AsTypeReference())], }, - ctor => ctor.WriteAssignment("ExcludedTargets", "targets") + ctor => ctor.Assignment("ExcludedTargets", "targets") ); body.XmlSummary("Gets or sets the targets to exclude for this parameter."); WritePublicProperty(body, "ExcludedTargets", targets.AsTypeReference()); - } + }, + summary: "Marks a parameter as excluded from the specified telemetry targets." ); } @@ -456,20 +605,24 @@ static void WriteActivitySourceGenerationAttribute(CodeWriter writer, TypeIdenti AttributeTargets.Assembly, body => { - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlSummary("Constructs a new instance specifying the activity source name and default behaviour."); + body.XmlParam("name", "The name of the activity source."); + body.XmlParam("defaultToTags", "Whether parameters are inferred as tags by default."); + body.XmlParam( + "generateDiagnosticsForMissingActivity", + "Whether diagnostics are generated for missing activity definitions." + ); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), - new ParameterDeclarationOptions( - "defaultToTags", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ) + new("name", PurviewTypeLibrary.System.String.AsTypeReference()), + new("defaultToTags", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "true", }, - new ParameterDeclarationOptions( + new( "generateDiagnosticsForMissingActivity", PurviewTypeLibrary.System.Boolean.AsTypeReference() ) @@ -480,35 +633,54 @@ static void WriteActivitySourceGenerationAttribute(CodeWriter writer, TypeIdenti }, ctor => { - ctor.WriteLine( - "if (string.IsNullOrWhiteSpace(name)) throw new System.ArgumentNullException(nameof(name));" + ctor.IfBlock( + "string.IsNullOrWhiteSpace(name)", + static body => body.Throw("new System.ArgumentNullException(nameof(name))") ); - ctor.WriteAssignment("Name", "name"); - ctor.WriteAssignment("DefaultToTags", "defaultToTags"); - ctor.WriteAssignment( + ctor.Assignment("Name", "name"); + ctor.Assignment("DefaultToTags", "defaultToTags"); + ctor.Assignment( "GenerateDiagnosticsForMissingActivity", "generateDiagnosticsForMissingActivity" ); } ); - WriteNullableStringProperty(body, "Name"); - WritePublicProperty(body, "DefaultToTags", PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true"); - WriteNullableStringProperty(body, "BaggageAndTagPrefix"); - WritePlainStringProperty(body, "BaggageAndTagSeparator", "\".\""); + WriteNullableStringProperty(body, "Name", "The name of the activity source."); + WritePublicProperty( + body, + "DefaultToTags", + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + "true", + "Determines whether parameters are inferred as tags by default." + ); + WriteNullableStringProperty( + body, + "BaggageAndTagPrefix", + "The prefix applied to generated baggage and tag names." + ); + WritePlainStringProperty( + body, + "BaggageAndTagSeparator", + "\".\"", + "The separator used between baggage and tag name parts." + ); WritePublicProperty( body, "LowercaseBaggageAndTagKeys", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether baggage and tag keys are lowercased." ); WritePublicProperty( body, "GenerateDiagnosticsForMissingActivity", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether diagnostics are generated for missing activity definitions." ); - } + }, + summary: "Specifies the default activity source generation behaviour for an assembly." ); } @@ -527,10 +699,14 @@ static void WriteActivitySourceAttribute(CodeWriter writer, TypeIdentity type) body.XmlParam("name", $"The {XmlSee("Name")}."); WriteNameConstructor(body, type); - WriteNullableStringProperty(body, "Name"); + WriteNullableStringProperty(body, "Name", "Optional. Gets the name of the activity source."); body.XmlSummary("Specifies the default when inferring between tag or baggage."); WritePublicProperty(body, "DefaultToTags", PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true"); - WriteNullableStringProperty(body, "BaggageAndTagPrefix"); + WriteNullableStringProperty( + body, + "BaggageAndTagPrefix", + "The prefix applied to generated baggage and tag names." + ); body.XmlSummary("Determines if the name is used as a prefix."); WritePublicProperty( body, @@ -545,7 +721,8 @@ static void WriteActivitySourceAttribute(CodeWriter writer, TypeIdentity type) PurviewTypeLibrary.System.Boolean.AsTypeReference(), "true" ); - } + }, + summary: "Marks an interface as an activity source." ); } @@ -559,29 +736,43 @@ static void WriteActivityAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Method, body => { + body.XmlSummary($"Constructs a new instance of the {XmlSee("ActivityAttribute")}."); WriteEmptyConstructor(body, type); + + body.XmlSummary($"Constructs a new instance specifying the {XmlSee("Name")}."); + body.XmlParam("name", $"The {XmlSee("Name")}."); WriteNameConstructor(body, type); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + + body.XmlSummary($"Constructs a new instance specifying the {XmlSee("Kind")}."); + body.XmlParam("kind", $"The {XmlSee("Kind")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = [new ParameterDeclarationOptions("kind", activityKind.AsTypeReference())], + Parameters = [new("kind", activityKind.AsTypeReference())], }, - ctor => ctor.WriteAssignment("Kind", "kind") + ctor => ctor.Assignment("Kind", "kind") ); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + + body.XmlSummary( + $"Constructs a new instance specifying the {XmlSee("Name")}, {XmlSee("Kind")} and whether the activity is created without starting it." + ); + body.XmlParam("name", $"The {XmlSee("Name")}."); + body.XmlParam("kind", $"The {XmlSee("Kind")}."); + body.XmlParam( + "createOnly", + $"Whether the activity is created without starting it ({XmlSee("CreateOnly")})." + ); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), - new ParameterDeclarationOptions("kind", activityKind.AsTypeReference()) + new("name", PurviewTypeLibrary.System.String.AsTypeReference()), + new("kind", activityKind.AsTypeReference()) { DefaultValue = $"{activityKind.RenderFullName}.Internal", }, - new ParameterDeclarationOptions( - "createOnly", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ) + new("createOnly", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "false", }, @@ -589,16 +780,27 @@ static void WriteActivityAttribute(CodeWriter writer, TypeIdentity type) }, ctor => { - ctor.WriteAssignment("Name", "name"); - ctor.WriteAssignment("Kind", "kind"); - ctor.WriteAssignment("CreateOnly", "createOnly"); + ctor.Assignment("Name", "name"); + ctor.Assignment("Kind", "kind"); + ctor.Assignment("CreateOnly", "createOnly"); } ); - WriteNullableStringProperty(body, "Name"); - WritePublicProperty(body, "Kind", activityKind.AsTypeReference()); - WritePublicProperty(body, "CreateOnly", PurviewTypeLibrary.System.Boolean.AsTypeReference()); - } + WriteNullableStringProperty(body, "Name", "Optional. Gets the name of the activity."); + WritePublicProperty( + body, + "Kind", + activityKind.AsTypeReference(), + summary: "Gets the kind of the activity." + ); + WritePublicProperty( + body, + "CreateOnly", + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + summary: "Determines whether the activity is created without starting it." + ); + }, + summary: "Marks a method as an activity." ); } @@ -612,40 +814,49 @@ static void WriteEventAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Method, body => { - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlSummary($"Constructs a new instance specifying the {XmlSee("StatusCode")}."); + body.XmlParam("statusCode", $"The {XmlSee("StatusCode")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions("statusCode", statusCode.AsTypeReference()) + new("statusCode", statusCode.AsTypeReference()) { DefaultValue = $"{statusCode.RenderFullName}.Unset", }, ], }, - ctor => ctor.WriteAssignment("StatusCode", "statusCode") + ctor => ctor.Assignment("StatusCode", "statusCode") ); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlSummary( + $"Constructs a new instance specifying the {XmlSee("Name")}, exception handling behaviour and {XmlSee("StatusCode")}." + ); + body.XmlParam("name", $"The {XmlSee("Name")}."); + body.XmlParam( + "useRecordExceptionRules", + $"Whether to use record exception rules ({XmlSee("UseRecordExceptionRules")})." + ); + body.XmlParam( + "recordExceptionAsEscaped", + $"Whether a recorded exception is escaped ({XmlSee("RecordExceptionAsEscaped")})." + ); + body.XmlParam("statusCode", $"The {XmlSee("StatusCode")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()), - new ParameterDeclarationOptions( - "useRecordExceptionRules", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ) + new("name", PurviewTypeLibrary.System.String.AsTypeReference()), + new("useRecordExceptionRules", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "true", }, - new ParameterDeclarationOptions( - "recordExceptionAsEscaped", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ) + new("recordExceptionAsEscaped", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "true", }, - new ParameterDeclarationOptions("statusCode", statusCode.AsTypeReference()) + new("statusCode", statusCode.AsTypeReference()) { DefaultValue = $"{statusCode.RenderFullName}.Unset", }, @@ -653,29 +864,41 @@ static void WriteEventAttribute(CodeWriter writer, TypeIdentity type) }, ctor => { - ctor.WriteAssignment("Name", "name"); - ctor.WriteAssignment("UseRecordExceptionRules", "useRecordExceptionRules"); - ctor.WriteAssignment("RecordExceptionAsEscaped", "recordExceptionAsEscaped"); - ctor.WriteAssignment("StatusCode", "statusCode"); + ctor.Assignment("Name", "name"); + ctor.Assignment("UseRecordExceptionRules", "useRecordExceptionRules"); + ctor.Assignment("RecordExceptionAsEscaped", "recordExceptionAsEscaped"); + ctor.Assignment("StatusCode", "statusCode"); } ); - WriteNullableStringProperty(body, "Name"); + WriteNullableStringProperty(body, "Name", "Optional. Gets the name of the event."); WritePublicProperty( body, "UseRecordExceptionRules", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether the default exception-handling rules are used." ); WritePublicProperty( body, "RecordExceptionAsEscaped", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether a recorded exception is escaped." ); - WritePublicProperty(body, "StatusCode", statusCode.AsTypeReference()); - WriteNullableStringProperty(body, "StatusDescription"); - } + WritePublicProperty( + body, + "StatusCode", + statusCode.AsTypeReference(), + summary: "Gets the status code of the event." + ); + WriteNullableStringProperty( + body, + "StatusDescription", + "Optional. Gets the status description of the event." + ); + }, + summary: "Marks a method as an activity event." ); } @@ -693,25 +916,39 @@ static void WriteLoggerGenerationAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Assembly, body => { - WriteEmptyConstructor(body, type); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + body.XmlSummary($"Constructs a new instance specifying the default {XmlSee("DefaultLevel")}."); + body.XmlParam("defaultLevel", $"The default {XmlSee("DefaultLevel")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = [new ParameterDeclarationOptions("defaultLevel", logLevel.AsTypeReference())], + Parameters = [new("defaultLevel", logLevel.AsTypeReference())], }, - ctor => ctor.WriteAssignment("DefaultLevel", "defaultLevel") + ctor => ctor.Assignment("DefaultLevel", "defaultLevel") ); WritePublicProperty( body, "DefaultLevel", logLevel.AsTypeReference(), - $"{logLevel.RenderFullName}.Information" + $"{logLevel.RenderFullName}.Information", + "Gets or sets the default log level used by generated log methods." + ); + WritePublicProperty( + body, + "GenerationMode", + TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference(), + summary: "Gets or sets the log generation mode used for generated log methods." + ); + WritePublicProperty( + body, + "DefaultPrefixType", + TypeLibrary.Logging.LogPrefixType.AsTypeReference(), + summary: "Gets or sets the default log prefix type used by generated log methods." ); - WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); - WritePublicProperty(body, "DefaultPrefixType", TypeLibrary.Logging.LogPrefixType.AsTypeReference()); }, - wrapInExcludeLoggingGuard: true + wrapInExcludeLoggingGuard: true, + summary: "Specifies the default logging generation behaviour for an assembly." ); } @@ -726,17 +963,19 @@ static void WriteLoggerAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Interface, body => { - WriteEmptyConstructor(body, type); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + body.XmlSummary( + $"Constructs a new instance specifying the default {XmlSee("DefaultLevel")} and an optional custom prefix." + ); + body.XmlParam("defaultLevel", $"The default {XmlSee("DefaultLevel")}."); + body.XmlParam("customPrefix", $"The custom log prefix ({XmlSee("CustomPrefix")})."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions("defaultLevel", logLevel.AsTypeReference()), - new ParameterDeclarationOptions( - "customPrefix", - PurviewTypeLibrary.System.String.AsTypeReference() - ) + new("defaultLevel", logLevel.AsTypeReference()), + new("customPrefix", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, @@ -744,11 +983,11 @@ static void WriteLoggerAttribute(CodeWriter writer, TypeIdentity type) }, ctor => { - ctor.WriteAssignment("DefaultLevel", "defaultLevel"); - ctor.WriteAssignment("CustomPrefix", "customPrefix"); - ctor.WriteIfBlock( + ctor.Assignment("DefaultLevel", "defaultLevel"); + ctor.Assignment("CustomPrefix", "customPrefix"); + ctor.IfBlock( "!string.IsNullOrWhiteSpace(CustomPrefix)", - block => block.WriteAssignment("PrefixType", $"{logPrefixType.RenderFullName}.Custom") + block => block.Assignment("PrefixType", $"{logPrefixType.RenderFullName}.Custom") ); } ); @@ -757,13 +996,29 @@ static void WriteLoggerAttribute(CodeWriter writer, TypeIdentity type) body, "DefaultLevel", logLevel.AsTypeReference(), - $"{logLevel.RenderFullName}.Information" + $"{logLevel.RenderFullName}.Information", + "Gets or sets the default log level used by generated log methods." + ); + WriteNullableStringProperty( + body, + "CustomPrefix", + "Gets or sets the custom log prefix used by generated log methods." + ); + WritePublicProperty( + body, + "PrefixType", + logPrefixType.AsTypeReference(), + summary: "Gets or sets the log prefix type used by generated log methods." + ); + WritePublicProperty( + body, + "GenerationMode", + TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference(), + summary: "Gets or sets the log generation mode used for generated log methods." ); - WriteNullableStringProperty(body, "CustomPrefix"); - WritePublicProperty(body, "PrefixType", logPrefixType.AsTypeReference()); - WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); }, - wrapInExcludeLoggingGuard: true + wrapInExcludeLoggingGuard: true, + summary: "Marks an interface as a logger." ); } @@ -777,64 +1032,66 @@ static void WriteLogAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Method, body => { - WriteEmptyConstructor(body, type); - WriteMessageTemplateConstructor(body, type); - WriteEventIdConstructor(body, type); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + WriteMessageTemplateConstructor( + body, + type, + $"Constructs a new instance specifying the {XmlSee("MessageTemplate")}." + ); + WriteEventIdConstructor(body, type, $"Constructs a new instance specifying the {XmlSee("EventId")}."); + body.XmlSummary( + $"Constructs a new instance specifying the {XmlSee("Level")}, optional {XmlSee("MessageTemplate")} and {XmlSee("Name")}." + ); + body.XmlParam("level", $"The {XmlSee("Level")}."); + body.XmlParam("messageTemplate", $"The {XmlSee("MessageTemplate")}."); + body.XmlParam("name", $"The {XmlSee("Name")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions("level", logLevel.AsTypeReference()), - new ParameterDeclarationOptions( - "messageTemplate", - PurviewTypeLibrary.System.String.AsTypeReference() - ) - { - DefaultValue = "null", - }, - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) + new("level", logLevel.AsTypeReference()), + new("messageTemplate", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, + new("name", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null" }, ], }, ctor => { - ctor.WriteAssignment("Level", "level"); - ctor.WriteAssignment("MessageTemplate", "messageTemplate"); - ctor.WriteAssignment("Name", "name"); + ctor.Assignment("Level", "level"); + ctor.Assignment("MessageTemplate", "messageTemplate"); + ctor.Assignment("Name", "name"); } ); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlSummary( + $"Constructs a new instance specifying the {XmlSee("EventId")}, {XmlSee("Level")}, optional {XmlSee("MessageTemplate")} and {XmlSee("Name")}." + ); + body.XmlParam("eventId", $"The {XmlSee("EventId")}."); + body.XmlParam("level", $"The {XmlSee("Level")}."); + body.XmlParam("messageTemplate", $"The {XmlSee("MessageTemplate")}."); + body.XmlParam("name", $"The {XmlSee("Name")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions( - "eventId", - PurviewTypeLibrary.System.Int32.AsTypeReference() - ), - new ParameterDeclarationOptions("level", logLevel.AsTypeReference()), - new ParameterDeclarationOptions( - "messageTemplate", - PurviewTypeLibrary.System.String.AsTypeReference() - ) - { - DefaultValue = "null", - }, - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) + new("eventId", PurviewTypeLibrary.System.Int32.AsTypeReference()), + new("level", logLevel.AsTypeReference()), + new("messageTemplate", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, + new("name", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null" }, ], }, ctor => { - ctor.WriteAssignment("Level", "level"); - ctor.WriteAssignment("MessageTemplate", "messageTemplate"); - ctor.WriteAssignment("EventId", "eventId"); - ctor.WriteAssignment("Name", "name"); + ctor.Assignment("Level", "level"); + ctor.Assignment("MessageTemplate", "messageTemplate"); + ctor.Assignment("EventId", "eventId"); + ctor.Assignment("Name", "name"); } ); @@ -842,18 +1099,34 @@ static void WriteLogAttribute(CodeWriter writer, TypeIdentity type) body, "Level", logLevel.AsTypeReference(), - $"{logLevel.RenderFullName}.Information" + $"{logLevel.RenderFullName}.Information", + "Gets or sets the log level of the log entry." + ); + WriteNullableStringProperty( + body, + "MessageTemplate", + "Gets or sets the message template used for the log entry." + ); + WritePublicProperty( + body, + "EventId", + PurviewTypeLibrary.System.Int32.MakeNullable(writer), + summary: "Gets or sets the event identifier of the log entry." + ); + WriteNullableStringProperty(body, "Name", "Gets or sets the name of the log entry."); + WritePublicProperty( + body, + "GenerationMode", + TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference(), + summary: "Gets or sets the log generation mode used for the log entry." ); - WriteNullableStringProperty(body, "MessageTemplate"); - WritePublicProperty(body, "EventId", PurviewTypeLibrary.System.Int32.MakeNullable(writer)); - WriteNullableStringProperty(body, "Name"); - WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); }, - wrapInExcludeLoggingGuard: true + wrapInExcludeLoggingGuard: true, + summary: "Marks a method as a log method." ); } - static void WriteSpecificLogAttribute(CodeWriter writer, TypeIdentity type) + static void WriteSpecificLogAttribute(CodeWriter writer, TypeIdentity type, string? summary = null) { EmitAttribute( writer, @@ -861,68 +1134,83 @@ static void WriteSpecificLogAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Method, body => { - WriteMessageTemplateConstructor(body, type); - WriteEventIdConstructor(body, type); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + WriteMessageTemplateConstructor( + body, + type, + $"Constructs a new instance specifying the {XmlSee("MessageTemplate")}." + ); + WriteEventIdConstructor(body, type, $"Constructs a new instance specifying the {XmlSee("EventId")}."); + body.XmlSummary( + $"Constructs a new instance specifying an optional {XmlSee("MessageTemplate")} and {XmlSee("Name")}." + ); + body.XmlParam("messageTemplate", $"The {XmlSee("MessageTemplate")}."); + body.XmlParam("name", $"The {XmlSee("Name")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions( - "messageTemplate", - PurviewTypeLibrary.System.String.AsTypeReference() - ) - { - DefaultValue = "null", - }, - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) + new("messageTemplate", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, + new("name", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null" }, ], }, ctor => { - ctor.WriteAssignment("MessageTemplate", "messageTemplate"); - ctor.WriteAssignment("Name", "name"); + ctor.Assignment("MessageTemplate", "messageTemplate"); + ctor.Assignment("Name", "name"); } ); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlSummary( + $"Constructs a new instance specifying the {XmlSee("EventId")}, optional {XmlSee("MessageTemplate")} and {XmlSee("Name")}." + ); + body.XmlParam("eventId", $"The {XmlSee("EventId")}."); + body.XmlParam("messageTemplate", $"The {XmlSee("MessageTemplate")}."); + body.XmlParam("name", $"The {XmlSee("Name")}."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions( - "eventId", - PurviewTypeLibrary.System.Int32.AsTypeReference() - ), - new ParameterDeclarationOptions( - "messageTemplate", - PurviewTypeLibrary.System.String.AsTypeReference() - ) - { - DefaultValue = "null", - }, - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) + new("eventId", PurviewTypeLibrary.System.Int32.AsTypeReference()), + new("messageTemplate", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, + new("name", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null" }, ], }, ctor => { - ctor.WriteAssignment("MessageTemplate", "messageTemplate"); - ctor.WriteAssignment("EventId", "eventId"); - ctor.WriteAssignment("Name", "name"); + ctor.Assignment("MessageTemplate", "messageTemplate"); + ctor.Assignment("EventId", "eventId"); + ctor.Assignment("Name", "name"); } ); - WriteNullableStringProperty(body, "MessageTemplate"); - WritePublicProperty(body, "EventId", PurviewTypeLibrary.System.Int32.MakeNullable(writer)); - WriteNullableStringProperty(body, "Name"); - WritePublicProperty(body, "GenerationMode", TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference()); + WriteNullableStringProperty( + body, + "MessageTemplate", + "Gets or sets the message template used for the log entry." + ); + WritePublicProperty( + body, + "EventId", + PurviewTypeLibrary.System.Int32.MakeNullable(writer), + summary: "Gets or sets the event identifier of the log entry." + ); + WriteNullableStringProperty(body, "Name", "Gets or sets the name of the log entry."); + WritePublicProperty( + body, + "GenerationMode", + TypeLibrary.Logging.LoggerGenerationMode.AsTypeReference(), + summary: "Gets or sets the log generation mode used for the log entry." + ); }, - wrapInExcludeLoggingGuard: true + wrapInExcludeLoggingGuard: true, + summary: summary ); } @@ -934,27 +1222,32 @@ static void WriteExpandEnumerableAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Parameter, body => { - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + body.XmlSummary( + $"Constructs a new instance specifying the maximum number of values to expand ({XmlSee("MaximumValueCount")})." + ); + body.XmlParam( + "maximumValueCount", + $"The maximum number of values to include ({XmlSee("MaximumValueCount")})." + ); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions( - "maximumValueCount", - PurviewTypeLibrary.System.Int32.AsTypeReference() - ) + new("maximumValueCount", PurviewTypeLibrary.System.Int32.AsTypeReference()) { DefaultValue = "5", }, ], }, - ctor => ctor.WriteAssignment("MaximumValueCount", "maximumValueCount") + ctor => ctor.Assignment("MaximumValueCount", "maximumValueCount") ); body.XmlSummary("Gets or sets the maximum number of values to include when expanding an enumerable."); WritePublicProperty(body, "MaximumValueCount", PurviewTypeLibrary.System.Int32.AsTypeReference()); }, - wrapInExcludeLoggingGuard: true + wrapInExcludeLoggingGuard: true, + summary: "Marks an enumerable parameter to be expanded into multiple log entries." ); } @@ -972,42 +1265,40 @@ static void WriteMeterGenerationAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Assembly, body => { - WriteEmptyConstructor(body, type); - - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + body.XmlSummary( + "Constructs a new instance specifying the meter name, name-generation type, instrument prefix and name casing defaults." + ); + body.XmlParam("meterName", $"The {XmlSee("MeterName")}."); + body.XmlParam("nameGenerationType", $"The {XmlSee("MeterNameGenerationType")}."); + body.XmlParam("instrumentPrefix", $"The {XmlSee("InstrumentPrefix")}."); + body.XmlParam( + "lowercaseInstrumentName", + $"Whether instrument names are lowercased ({XmlSee("LowercaseInstrumentName")})." + ); + body.XmlParam("lowercaseTagKeys", $"Whether tag keys are lowercased ({XmlSee("LowercaseTagKeys")})."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = [ - new ParameterDeclarationOptions( - "meterName", - PurviewTypeLibrary.System.String.AsTypeReference() - ) + new("meterName", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, - new ParameterDeclarationOptions("nameGenerationType", nameGenerationType.AsTypeReference()) + new("nameGenerationType", nameGenerationType.AsTypeReference()) { DefaultValue = $"{nameGenerationType.RenderFullName}.DotNet", }, - new ParameterDeclarationOptions( - "instrumentPrefix", - PurviewTypeLibrary.System.String.AsTypeReference() - ) + new("instrumentPrefix", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null", }, - new ParameterDeclarationOptions( - "lowercaseInstrumentName", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ) + new("lowercaseInstrumentName", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "true", }, - new ParameterDeclarationOptions( - "lowercaseTagKeys", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ) + new("lowercaseTagKeys", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "true", }, @@ -1015,36 +1306,49 @@ static void WriteMeterGenerationAttribute(CodeWriter writer, TypeIdentity type) }, ctor => { - ctor.WriteAssignment("MeterName", "meterName"); - ctor.WriteAssignment("MeterNameGenerationType", "nameGenerationType"); - ctor.WriteAssignment("InstrumentPrefix", "instrumentPrefix"); - ctor.WriteAssignment("LowercaseInstrumentName", "lowercaseInstrumentName"); - ctor.WriteAssignment("LowercaseTagKeys", "lowercaseTagKeys"); + ctor.Assignment("MeterName", "meterName"); + ctor.Assignment("MeterNameGenerationType", "nameGenerationType"); + ctor.Assignment("InstrumentPrefix", "instrumentPrefix"); + ctor.Assignment("LowercaseInstrumentName", "lowercaseInstrumentName"); + ctor.Assignment("LowercaseTagKeys", "lowercaseTagKeys"); } ); - WriteNullableStringProperty(body, "MeterName"); + WriteNullableStringProperty(body, "MeterName", "Gets or sets the name of the meter."); WritePublicProperty( body, "MeterNameGenerationType", nameGenerationType.AsTypeReference(), - $"{nameGenerationType.RenderFullName}.DotNet" + $"{nameGenerationType.RenderFullName}.DotNet", + "Gets or sets how meter names are generated when not explicitly specified." + ); + WriteNullableStringProperty( + body, + "InstrumentPrefix", + "Gets or sets the prefix applied to instrument names." + ); + WritePlainStringProperty( + body, + "InstrumentSeparator", + "\".\"", + "Gets or sets the separator used between instrument name parts." ); - WriteNullableStringProperty(body, "InstrumentPrefix"); - WritePlainStringProperty(body, "InstrumentSeparator", "\".\""); WritePublicProperty( body, "LowercaseInstrumentName", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether instrument names are lowercased." ); WritePublicProperty( body, "LowercaseTagKeys", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether tag keys are lowercased." ); - } + }, + summary: "Specifies the default meter generation behaviour for an assembly." ); } @@ -1056,34 +1360,42 @@ static void WriteMeterAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Interface, body => { - WriteEmptyConstructor(body, type); - WriteNameConstructor(body, type); + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + WriteNameConstructor(body, type, $"Constructs a new instance specifying the {XmlSee("Name")}."); - WriteNullableStringProperty(body, "Name"); - WriteNullableStringProperty(body, "InstrumentPrefix"); + WriteNullableStringProperty(body, "Name", "Gets or sets the name of the meter."); + WriteNullableStringProperty( + body, + "InstrumentPrefix", + "Gets or sets the prefix applied to instrument names." + ); WritePublicProperty( body, "IncludeAssemblyInstrumentPrefix", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether the assembly-level instrument prefix is included." ); WritePublicProperty( body, "LowercaseInstrumentName", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether instrument names are lowercased." ); WritePublicProperty( body, "LowercaseTagKeys", PurviewTypeLibrary.System.Boolean.AsTypeReference(), - "true" + "true", + "Determines whether tag keys are lowercased." ); - } + }, + summary: "Marks an interface as a meter." ); } - static void WriteAutoCounterAttribute(CodeWriter writer, TypeIdentity type) + static void WriteAutoCounterAttribute(CodeWriter writer, TypeIdentity type, string? summary = null) { EmitAttribute( writer, @@ -1091,17 +1403,22 @@ static void WriteAutoCounterAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Method, body => { - WriteEmptyConstructor(body, type); - WriteNameUnitDescriptionConstructor(body, type); + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + WriteNameUnitDescriptionConstructor( + body, + type, + summary: $"Constructs a new instance specifying the {XmlSee("Name")}, {XmlSee("Unit")} and {XmlSee("Description")}." + ); - WriteNullableStringProperty(body, "Name"); - WriteNullableStringProperty(body, "Unit"); - WriteNullableStringProperty(body, "Description"); - } + WriteNullableStringProperty(body, "Name", "Gets or sets the name of the instrument."); + WriteNullableStringProperty(body, "Unit", "Gets or sets the measurement unit of the instrument."); + WriteNullableStringProperty(body, "Description", "Gets or sets the description of the instrument."); + }, + summary: summary ); } - static void WriteCounterLikeAttribute(CodeWriter writer, TypeIdentity type) + static void WriteCounterLikeAttribute(CodeWriter writer, TypeIdentity type, string? summary = null) { EmitAttribute( writer, @@ -1109,31 +1426,40 @@ static void WriteCounterLikeAttribute(CodeWriter writer, TypeIdentity type) AttributeTargets.Method, body => { - WriteEmptyConstructor(body, type); - body.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + body.XmlSummary( + $"Constructs a new instance specifying whether the counter auto-increments ({XmlSee("AutoIncrement")})." + ); + body.XmlParam("autoIncrement", $"Whether the counter auto-increments ({XmlSee("AutoIncrement")})."); + body.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { - Parameters = - [ - new ParameterDeclarationOptions( - "autoIncrement", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ), - ], + Parameters = [new("autoIncrement", PurviewTypeLibrary.System.Boolean.AsTypeReference())], }, - ctor => ctor.WriteAssignment("AutoIncrement", "autoIncrement") + ctor => ctor.Assignment("AutoIncrement", "autoIncrement") + ); + WriteNameUnitDescriptionConstructor( + body, + type, + appendAutoIncrement: true, + summary: $"Constructs a new instance specifying the {XmlSee("Name")}, {XmlSee("Unit")}, {XmlSee("Description")} and whether the counter auto-increments." ); - WriteNameUnitDescriptionConstructor(body, type, appendAutoIncrement: true); - WritePublicProperty(body, "AutoIncrement", PurviewTypeLibrary.System.Boolean.AsTypeReference()); - WriteNullableStringProperty(body, "Name"); - WriteNullableStringProperty(body, "Unit"); - WriteNullableStringProperty(body, "Description"); - } + WritePublicProperty( + body, + "AutoIncrement", + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + summary: "Determines whether the counter auto-increments." + ); + WriteNullableStringProperty(body, "Name", "Gets or sets the name of the instrument."); + WriteNullableStringProperty(body, "Unit", "Gets or sets the measurement unit of the instrument."); + WriteNullableStringProperty(body, "Description", "Gets or sets the description of the instrument."); + }, + summary: summary ); } - static void WriteObservableCounterLikeAttribute(CodeWriter writer, TypeIdentity type) + static void WriteObservableCounterLikeAttribute(CodeWriter writer, TypeIdentity type, string? summary = null) { EmitAttribute( writer, @@ -1141,19 +1467,30 @@ static void WriteObservableCounterLikeAttribute(CodeWriter writer, TypeIdentity AttributeTargets.Method, body => { - WriteEmptyConstructor(body, type); - WriteNameUnitDescriptionConstructor(body, type, appendThrowOnAlreadyInitialized: true); - - WritePublicProperty(body, "AutoIncrement", PurviewTypeLibrary.System.Boolean.AsTypeReference()); - WriteNullableStringProperty(body, "Name"); - WriteNullableStringProperty(body, "Unit"); - WriteNullableStringProperty(body, "Description"); + WriteEmptyConstructor(body, type, $"Constructs a new instance of the {XmlSee(type.Name)}."); + WriteNameUnitDescriptionConstructor( + body, + type, + appendThrowOnAlreadyInitialized: true, + summary: $"Constructs a new instance specifying the {XmlSee("Name")}, {XmlSee("Unit")}, {XmlSee("Description")} and whether initializing twice throws." + ); + WritePublicProperty( + body, + "AutoIncrement", + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + summary: "Determines whether the counter auto-increments." + ); + WriteNullableStringProperty(body, "Name", "Gets or sets the name of the instrument."); + WriteNullableStringProperty(body, "Unit", "Gets or sets the measurement unit of the instrument."); + WriteNullableStringProperty(body, "Description", "Gets or sets the description of the instrument."); WritePublicProperty( body, "ThrowOnAlreadyInitialized", - PurviewTypeLibrary.System.Boolean.AsTypeReference() + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + summary: "Determines whether an exception is thrown when the instrument is initialized more than once." ); - } + }, + summary: summary ); } @@ -1161,23 +1498,38 @@ static void WriteNameUnitDescriptionConstructor( CodeWriter writer, TypeIdentity type, bool appendAutoIncrement = false, - bool appendThrowOnAlreadyInitialized = false + bool appendThrowOnAlreadyInitialized = false, + string? summary = null ) { - writer.WriteConstructor( - new ConstructorDeclarationOptions(type.Name, TypeDeclarationAccessibility.Public) + if (summary != null) + { + writer.XmlSummary(summary); + writer.XmlParam("name", "The name of the instrument."); + writer.XmlParam("unit", "The measurement unit of the instrument."); + writer.XmlParam("description", "The description of the instrument."); + if (appendAutoIncrement) + writer.XmlParam("autoIncrement", "Whether the counter should auto-increment."); + if (appendThrowOnAlreadyInitialized) + writer.XmlParam( + "throwOnAlreadyInitialized", + "Whether to throw if the instrument has already been initialized." + ); + } + writer.Constructor( + new(type.Name, TypeDeclarationAccessibility.Public) { Parameters = BuildNameUnitDescriptionParameters(appendAutoIncrement, appendThrowOnAlreadyInitialized), }, ctor => { - ctor.WriteAssignment("Name", "name"); - ctor.WriteAssignment("Unit", "unit"); - ctor.WriteAssignment("Description", "description"); + ctor.Assignment("Name", "name"); + ctor.Assignment("Unit", "unit"); + ctor.Assignment("Description", "description"); if (appendAutoIncrement) - ctor.WriteAssignment("AutoIncrement", "autoIncrement"); + ctor.Assignment("AutoIncrement", "autoIncrement"); if (appendThrowOnAlreadyInitialized) - ctor.WriteAssignment("ThrowOnAlreadyInitialized", "throwOnAlreadyInitialized"); + ctor.Assignment("ThrowOnAlreadyInitialized", "throwOnAlreadyInitialized"); } ); } @@ -1188,34 +1540,20 @@ bool appendThrowOnAlreadyInitialized ) { var parameters = ImmutableArray.Empty; + parameters = parameters.Add(new("name", PurviewTypeLibrary.System.String.AsTypeReference())); parameters = parameters.Add( - new ParameterDeclarationOptions("name", PurviewTypeLibrary.System.String.AsTypeReference()) - ); - parameters = parameters.Add( - new ParameterDeclarationOptions("unit", PurviewTypeLibrary.System.String.AsTypeReference()) - { - DefaultValue = "null", - } + new("unit", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null" } ); parameters = parameters.Add( - new ParameterDeclarationOptions("description", PurviewTypeLibrary.System.String.AsTypeReference()) - { - DefaultValue = "null", - } + new("description", PurviewTypeLibrary.System.String.AsTypeReference()) { DefaultValue = "null" } ); if (appendAutoIncrement) parameters = parameters.Add( - new ParameterDeclarationOptions("autoIncrement", PurviewTypeLibrary.System.Boolean.AsTypeReference()) - { - DefaultValue = "false", - } + new("autoIncrement", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "false" } ); if (appendThrowOnAlreadyInitialized) parameters = parameters.Add( - new ParameterDeclarationOptions( - "throwOnAlreadyInitialized", - PurviewTypeLibrary.System.Boolean.AsTypeReference() - ) + new("throwOnAlreadyInitialized", PurviewTypeLibrary.System.Boolean.AsTypeReference()) { DefaultValue = "false", } @@ -1231,77 +1569,96 @@ bool appendThrowOnAlreadyInitialized static void WriteTargetsEnum(CodeWriter writer, TypeIdentity type) { writer - .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .FileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) .XmlSummary("Determines which telemetry targets a parameter is excluded from.") - .WriteEnum( - new(type.Name, TypeDeclarationAccessibility.Public) - { - Attributes = [new(new TypeIdentity("FlagsAttribute", "System"))], - }, - new("None", 0, "No telemetry targets are excluded."), - new("Activities", 1, "Excludes activity (tracing) targets."), - new("Logging", 2, "Excludes logging targets."), - new("Metrics", 4, "Excludes metrics targets."), - new("All", "Activities | Logging | Metrics") + .Enum( + type.Name, + TypeDeclarationAccessibility.Public, + fields: + [ + new("None", 0, "No telemetry targets are excluded."), + new("Activities", 1, "Excludes activity (tracing) targets."), + new("Logging", 2, "Excludes logging targets."), + new("Metrics", 4, "Excludes metrics targets."), + new("All", "Activities | Logging | Metrics"), + ], + configure: options => options with { Attributes = [new(new TypeIdentity("FlagsAttribute", "System"))] } ); } static void WriteNamingConventionEnum(CodeWriter writer, TypeIdentity type) { writer - .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .FileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) .XmlSummary("Determines the naming convention used for generated telemetry names.") - .WriteEnum( - new(type.Name, TypeDeclarationAccessibility.Public), - new("Legacy", 0, "Uses the legacy naming convention for generated telemetry names."), - new("OpenTelemetry", 1, "Uses the OpenTelemetry naming convention for generated telemetry names.") + .Enum( + type.Name, + TypeDeclarationAccessibility.Public, + fields: + [ + new("Legacy", 0, "Uses the legacy naming convention for generated telemetry names."), + new("OpenTelemetry", 1, "Uses the OpenTelemetry naming convention for generated telemetry names."), + ] ); } static void WriteLogPrefixTypeEnum(CodeWriter writer, TypeIdentity type) { - writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); - 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, "Uses the default log prefix."), - new("Interface", 1, "Uses the interface name as the log prefix."), - new("Class", 2, "Uses the class name as the log prefix."), - new("Custom", 3, "Uses a custom log prefix."), - new("TrimmedClassName", 4, "Uses the trimmed class name as the log prefix.") - ); - - writer.WriteLine("#endif"); + writer.HashDefines( + "!EXCLUDE_PURVIEW_TELEMETRY_LOGGING", + hashWriter => + hashWriter + .FileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .XmlSummary("Determines the mode used to generate or override the prefix for the log entry.") + .Enum( + type.Name, + TypeDeclarationAccessibility.Public, + fields: + [ + new("Default", 0, "Uses the default log prefix."), + new("Interface", 1, "Uses the interface name as the log prefix."), + new("Class", 2, "Uses the class name as the log prefix."), + new("Custom", 3, "Uses a custom log prefix."), + new("TrimmedClassName", 4, "Uses the trimmed class name as the log prefix."), + ] + ) + ); } static void WriteLoggerGenerationModeEnum(CodeWriter writer, TypeIdentity type) { - writer.WriteLine("#if !EXCLUDE_PURVIEW_TELEMETRY_LOGGING").NewLine(); - - writer - .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) - .XmlSummary("Controls the generation mode used for log methods.") - .WriteEnum( - new(type.Name, TypeDeclarationAccessibility.Public), - new("Auto", 0, "Automatically selects the log generation mode."), - new("V1", 1, "Uses the first-generation log implementation."), - new("V2", 2, "Uses the second-generation log implementation.") - ); - - writer.WriteLine("#endif"); + writer.HashDefines( + "!EXCLUDE_PURVIEW_TELEMETRY_LOGGING", + hashWriter => + hashWriter + .FileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .XmlSummary("Controls the generation mode used for log methods.") + .Enum( + type.Name, + TypeDeclarationAccessibility.Public, + fields: + [ + new("Auto", 0, "Automatically selects the log generation mode."), + new("V1", 1, "Uses the first-generation log implementation."), + new("V2", 2, "Uses the second-generation log implementation."), + ] + ) + ); } static void WriteMeterNameGenerationTypeEnum(CodeWriter writer, TypeIdentity type) { writer - .WriteFileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) + .FileScopedNamespace(TypeLibrary.PurviewTelemetryNamespace) .XmlSummary("Determines how meter names are generated when not explicitly specified.") - .WriteEnum( - new(type.Name, TypeDeclarationAccessibility.Public), - new("OpenTelemetry", 0, "Generates meter names using the OpenTelemetry convention."), - new("DotNet", 1, "Generates meter names using the .NET convention.") + .Enum( + type.Name, + TypeDeclarationAccessibility.Public, + fields: + [ + new("OpenTelemetry", 0, "Generates meter names using the OpenTelemetry convention."), + new("DotNet", 1, "Generates meter names using the .NET convention."), + ] ); } } diff --git a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs index 736b2624..40d50b89 100644 --- a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.Methods.cs @@ -111,7 +111,7 @@ SourceProductionContext context writer.NewLine(); using ( - writer.WriteMethodScope( + writer.MethodScope( new MethodDeclarationOptions( methodName, returnType, @@ -154,15 +154,10 @@ SourceProductionContext context // if (!_logger.IsEnabled(LogLevel.Information))) // { return; }; // ...but only if it's not been scoped. - writer - .Write("if (!") - .Write(PropertyLibrary.Logging.LoggerFieldName) - .Write(".IsEnabled(") - .Write(methodTarget.MSLevel) - .WriteLine("))"); - - using (writer.OpenBlockScope()) - writer.WriteLine("return;"); + writer.IfBlock( + "!" + PropertyLibrary.Logging.LoggerFieldName + ".IsEnabled(" + methodTarget.MSLevel + ")", + static body => body.Return() + ); writer.NewLine(); } @@ -232,26 +227,27 @@ SourceProductionContext context if (variables.Length > 0) { foreach (var variableDefinition in variables) - writer.WriteLine(variableDefinition); + writer.Line(variableDefinition); writer.NewLine(); } var formattedMessageVarName = FindUniqueName("formattedMessage", existingParamNames); - writer - .Write("var ") - .WriteLine("formattedMessage = ") - .WriteLine("#if NET") - .Write("string.Create(global::System.Globalization.CultureInfo.InvariantCulture, $") - .Write(interpolatedMessage.Wrap()) - .WriteLine(");") - .WriteLine("#else") - .Write("global::System.FormattableString.Invariant($") - .Write(interpolatedMessage.Wrap()) - .WriteLine(");") - .WriteLine("#endif") - .Write(";") - .NewLine(); + writer.Write("var ").Line("formattedMessage = "); + + using (writer.HashDefinesScope("NET")) + { + writer + .Write("string.Create(global::System.Globalization.CultureInfo.InvariantCulture, $") + .Write(interpolatedMessage.Wrap()) + .Line(");"); + + writer.HashElse(); + + writer.Write("global::System.FormattableString.Invariant($").Write(interpolatedMessage.Wrap()).Line(");"); + } + + writer.Write(";").NewLine(); OutputState( writer, @@ -267,7 +263,7 @@ SourceProductionContext context .Write(PropertyLibrary.Logging.LoggerFieldName) .Write(".BeginScope(") .Write(stateVarName) - .WriteLine(");"); + .Line(");"); } static void EmitNonScopedBody( @@ -300,7 +296,7 @@ bool useTypedState var eventId = methodTarget.EventId ?? SharedHelpers.GetNonRandomizedHashCode(methodTarget.MethodName); writer .Write(PropertyLibrary.Logging.LoggerFieldName) - .WriteLine(".Log(") + .Line(".Log(") // Log level .Write(methodTarget.MSLevel.WithComma(andSpace: false)) // Event Id @@ -312,7 +308,7 @@ writer.IsNullableContextEnabled is null or true .Write(eventId.ToString(CultureInfo.InvariantCulture)) .Write(", nameof(") .Write(methodTarget.LogName) - .WriteLine(")),") + .Line(")),") // State .Write(stateVarName.WithComma(andSpace: false)) // Exception @@ -324,31 +320,35 @@ writer.IsNullableContextEnabled is null or true .Write(expressionStateVarName) .Write(", ") .Write(expressionExceptionVarName ?? "_") - .WriteLine(") =>") - .WriteLine("{"); + .Line(") =>") + .Line("{"); if (variables.Length > 0) { foreach (var variableDefinition in variables) - writer.WriteLine(variableDefinition); + writer.Line(variableDefinition); writer.NewLine(); } - writer - .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().Write(stateVarName).Write(".Clear();").NewLine(); + using (writer.HashDefinesScope("NET")) + { + writer + .Write("return string.Create(global::System.Globalization.CultureInfo.InvariantCulture, $") + .Write(interpolatedMessage.Wrap()) + .Line(");"); + + writer.HashElse(); + + writer + .Write("return global::System.FormattableString.Invariant($") + .Write(interpolatedMessage.Wrap()) + .Line(");"); + } + + writer.Write("}").Write(");"); + + writer.NewLine().MethodCallOn(stateVarName, "Clear").NewLine(); } static void EmitTypedStateLogCall( @@ -373,7 +373,7 @@ [.. methodTarget.Parameters] writer .Write(PropertyLibrary.Logging.LoggerFieldName) - .WriteLine(".Log(") + .Line(".Log(") .Write(methodTarget.MSLevel.WithComma(andSpace: false)) .Write( writer.IsNullableContextEnabled is null or true @@ -383,7 +383,7 @@ writer.IsNullableContextEnabled is null or true .Write(eventId.ToString(CultureInfo.InvariantCulture)) .Write(", nameof(") .Write(methodTarget.LogName) - .WriteLine(")),") + .Line(")),") .Write("new ") .Write(structName) .Write('('); @@ -395,7 +395,7 @@ writer.IsNullableContextEnabled is null or true writer.Write(", "); } - writer.WriteLine("),"); + writer.Line("),"); writer.Write(methodTarget.ExceptionParameter.OrNullKeyword().WithComma(andSpace: false)); writer @@ -403,19 +403,23 @@ writer.IsNullableContextEnabled is null or true .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(");"); + .Line(") =>") + .Line("{"); + + using (writer.HashDefinesScope("NET")) + { + writer + .Write("return string.Create(global::System.Globalization.CultureInfo.InvariantCulture, $") + .Write(interpolatedMessage.Wrap()) + .Line(");"); + writer.HashElse(); + + writer + .Write("return global::System.FormattableString.Invariant($") + .Write(interpolatedMessage.Wrap()) + .Line(");"); + } + writer.Write("}").Write(");"); writer.NewLine(); } @@ -451,11 +455,11 @@ SourceProductionContext context .Write(" = ") .Write(TypeLibrary.Logging.MicrosoftExtensions.LoggerMessageHelper) .Write('.') - .WriteLine("ThreadLocalState;") + .Line("ThreadLocalState;") .Write(stateVarName) .Write(".ReserveTagSpace(") .Write(reservationCount.ToString(CultureInfo.InvariantCulture)) - .WriteLine(");") + .Line(");") .NewLine(); // Original format is always at 0. @@ -508,7 +512,7 @@ SourceProductionContext context foreach (var nullableLogProperty in postSetProperties) { context.CancellationToken.ThrowIfCancellationRequested(); - writer.WriteLine(nullableLogProperty); + writer.Line(nullableLogProperty); } } @@ -544,28 +548,17 @@ List existingParamNames if (shouldSkipNull) { var tmpVarName = FindUniqueName("tmp", existingParamNames); - logPropertiesWriter - .Write("{") - .Write("var ") - .Write(tmpVarName) - .Write(" = ") - .Write(logPropertyValue) - .WriteLine(";") - .Write("if (") - .Write(tmpVarName) - .WriteLine(" != null)") - .Write("{"); - logPropertiesWriter.Indent(); - - logPropertyValue = tmpVarName; + logPropertiesWriter.Write("{"); + logPropertiesWriter.Write("var ").Write(tmpVarName).Write(" = ").Write(logPropertyValue).Line(";"); + logPropertiesWriter.IfBlock( + tmpVarName + " != null", + body => OutputState(body, stateVarName, logPropertyName.Wrap(), tmpVarName, null) + ); + logPropertiesWriter.Write("}"); } - - OutputState(logPropertiesWriter, stateVarName, logPropertyName.Wrap(), logPropertyValue, null); - - if (shouldSkipNull) + else { - logPropertiesWriter.Unindent(); - logPropertiesWriter.Write("}").Write("}"); + OutputState(logPropertiesWriter, stateVarName, logPropertyName.Wrap(), logPropertyValue, null); } postPropertyDefinitions ??= []; @@ -639,71 +632,52 @@ LoggerOutputContext output CodeWriter snippet = new(GenerationSettings.Create(), throwOnUnclosedScopes: false); var iteratorVarName = FindUniqueName("tmp_i", existingParamNames); var iteratorItemVarName = FindUniqueName("item", existingParamNames); - snippet.Write("if (").Write(parameter.Name).WriteLine(" != null)").Write("{"); - snippet.Indent(); - snippet.Write("var ").Write(iteratorVarName).WriteLine(" = 0;"); + snippet.IfBlock( + parameter.Name + " != null", + body => + { + body.Write("var ").Write(iteratorVarName).Line(" = 0;"); - var maxCount = parameter.ExpandEnumerableAttribute!.Value.MaximumValueCount; + var maxCount = parameter.ExpandEnumerableAttribute!.Value.MaximumValueCount; - if (maxCount < 1) - maxCount = 1; + if (maxCount < 1) + maxCount = 1; - if (maxCount > PropertyLibrary.Logging.UnboundedIEnumerableMaxCountBeforeDiagnostic) - { - output.Context.Diagnostic($"Identified {parameter.Name} that has a large unbounded ienumerable max."); - } + if (maxCount > PropertyLibrary.Logging.UnboundedIEnumerableMaxCountBeforeDiagnostic) + { + output.Context.Diagnostic( + $"Identified {parameter.Name} that has a large unbounded ienumerable max." + ); + } - snippet - .Write("foreach (var ") - .Write(iteratorItemVarName) - .Write(" in ") - .Write(parameter.Name) - .WriteLine(")") - .Write("{"); - snippet.Indent(); - snippet - .Write("if (") - .Write(iteratorVarName) - .Write(" == ") - .Write(maxCount.ToString(CultureInfo.InvariantCulture)) - .WriteLine(")"); - snippet.WriteLine("{"); - snippet.Indent(); - snippet.WriteLine("break;"); - snippet.Unindent(); - snippet.WriteLine("}"); - - snippet.NewLine(); - - OutputState(snippet, stateVarName, $"$\"{parameter.Name}[{{{iteratorVarName}}}]\"", iteratorItemVarName, null); - - snippet.Write(iteratorVarName).WriteLine("++;"); - - snippet.Unindent(); - snippet.Write("}"); - snippet.Unindent(); - snippet.Write("}"); + body.Foreach( + "var " + iteratorItemVarName + " in " + parameter.Name, + loopBody => + { + loopBody.IfBlock( + iteratorVarName + " == " + maxCount.ToString(CultureInfo.InvariantCulture), + static breakBody => breakBody.Line("break;") + ); + + loopBody.NewLine(); + + OutputState( + loopBody, + stateVarName, + $"$\"{parameter.Name}[{{{iteratorVarName}}}]\"", + iteratorItemVarName, + null + ); + + loopBody.Write(iteratorVarName).Line("++;"); + } + ); + } + ); return snippet.ToString().TrimEnd('\n'); } - static void EmitParametersAsMethodArgumentList( - LogMethodTarget methodTarget, - CodeWriter writer, - SourceProductionContext context - ) - { - for (var i = 0; i < methodTarget.TotalParameterCount; i++) - { - context.CancellationToken.ThrowIfCancellationRequested(); - - writer.Write(methodTarget.Parameters[i].ParameterType).Write(' ').Write(methodTarget.Parameters[i].Name); - - if (i < methodTarget.TotalParameterCount - 1) - writer.Write(", "); - } - } - static string FindUniqueName(string name, List existingValues) { var i = 0; @@ -888,7 +862,7 @@ static void EmitLogStateStruct(CodeWriter writer, LogMethodTarget methodTarget, writer.NewLine(); using ( - writer.WriteStructScope( + writer.StructScope( new TypeDeclarationOptions(structName, TypeDeclarationAccessibility.Private) { IsReadOnly = true, @@ -898,10 +872,15 @@ static void EmitLogStateStruct(CodeWriter writer, LogMethodTarget methodTarget, ) ) { - writer - .Write("static readonly string s_originalFormat = ") - .Write(methodTarget.MessageTemplate.Wrap()) - .WriteLine(";"); + writer.Field( + new FieldDeclarationOptions("s_originalFormat", PurviewTypeLibrary.System.String.AsTypeReference()) + { + IsStatic = true, + IsReadOnly = true, + Initializer = methodTarget.MessageTemplate.Wrap(), + IncludeGeneratedAttributes = false, + } + ); if (nonExceptionParams.Count > 0) { @@ -911,103 +890,115 @@ static void EmitLogStateStruct(CodeWriter writer, LogMethodTarget methodTarget, { context.CancellationToken.ThrowIfCancellationRequested(); - writer - .Write("public readonly ") - .Write(param.ParameterType) - .Write(" _") - .Write(param.UpperCasedName) - .WriteLine(";"); - } - - writer.NewLine().Write("public ").Write(structName).Write('('); - - for (var i = 0; i < nonExceptionParams.Count; i++) - { - context.CancellationToken.ThrowIfCancellationRequested(); - - writer.Write(nonExceptionParams[i].ParameterType).Write(' ').Write(nonExceptionParams[i].Name); - - if (i < nonExceptionParams.Count - 1) - writer.Write(", "); + writer.Field( + new FieldDeclarationOptions($"_{param.UpperCasedName}", param.ParameterType) + { + Accessibility = TypeDeclarationAccessibility.Public, + IsReadOnly = true, + IncludeGeneratedAttributes = false, + } + ); } - writer.Write(")"); + writer.NewLine(); - using (writer.OpenBlockScope()) - { - foreach (var param in nonExceptionParams) + writer.Constructor( + new ConstructorDeclarationOptions(structName, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + .. nonExceptionParams.Select(p => new ParameterDeclarationOptions(p.Name, p.ParameterType)), + ], + IncludeGeneratedAttributes = false, + }, + ctor => { - context.CancellationToken.ThrowIfCancellationRequested(); + foreach (var param in nonExceptionParams) + { + context.CancellationToken.ThrowIfCancellationRequested(); - writer.Write("_").Write(param.UpperCasedName).Write(" = ").Write(param.Name).WriteLine(";"); + ctor.Write("_").Write(param.UpperCasedName).Write(" = ").Write(param.Name).Line(";"); + } } - } + ); } writer .NewLine() .NewLine() - .Write("public int Count => ") - .Write(count.ToString(CultureInfo.InvariantCulture)) - .WriteLine(";") - .NewLine() - .Write($"public {kvpType} this[int index]"); - - using (writer.OpenBlockScope()) - { - if (writer.IsNullableContextEnabled is null or true) - { - writer.WriteLine("get => index switch {"); - writer.Indent(); - writer.WriteLine("0 => new(\"{OriginalFormat}\", s_originalFormat),"); - - for (var i = 0; i < nonExceptionParams.Count; i++) + .Property( + new PropertyDeclarationOptions( + "Count", + PurviewTypeLibrary.System.Int32.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) { - context.CancellationToken.ThrowIfCancellationRequested(); - - writer - .Write($"{i + 1} => new(") - .Write(nonExceptionParams[i].Name.Wrap()) - .Write(", _") - .Write(nonExceptionParams[i].UpperCasedName) - .WriteLine("),"); + ExpressionBody = count.ToString(CultureInfo.InvariantCulture), + IncludeGeneratedAttributes = false, } + ) + .NewLine(); - writer.WriteLine("_ => throw new global::System.IndexOutOfRangeException(nameof(index))"); - writer.Unindent(); - writer.WriteLine("};"); - } - else + writer.Indexer( + new IndexerDeclarationOptions( + new TypeReference(new TypeIdentity(kvpType, null)), + new ParameterDeclarationOptions("index", PurviewTypeLibrary.System.Int32.AsTypeReference()) + ) { - writer.Write("get"); - using (writer.OpenBlockScope()) + Accessibility = TypeDeclarationAccessibility.Public, + IncludeGeneratedAttributes = false, + }, + getter => + { + if (writer.IsNullableContextEnabled is null or true) { - writer.Write("switch (index)"); - using (writer.OpenBlockScope()) + getter.Line("return index switch {"); + getter.Indent(); + getter.Line("0 => new(\"{OriginalFormat}\", s_originalFormat),"); + + for (var i = 0; i < nonExceptionParams.Count; i++) { - writer.WriteLine( - "case 0: return new " + kvpType + "(\"{OriginalFormat}\", s_originalFormat);" - ); + context.CancellationToken.ThrowIfCancellationRequested(); + + getter + .Write($"{i + 1} => new(") + .Write(nonExceptionParams[i].Name.Wrap()) + .Write(", _") + .Write(nonExceptionParams[i].UpperCasedName) + .Line("),"); + } + + getter.Line("_ => throw new global::System.IndexOutOfRangeException(nameof(index))"); + getter.Unindent(); + getter.Line("};"); + } + else + { + getter.Write("switch (index)"); + using (getter.OpenBlockScope()) + { + getter.Line("case 0: return new " + kvpType + "(\"{OriginalFormat}\", s_originalFormat);"); for (var i = 0; i < nonExceptionParams.Count; i++) { context.CancellationToken.ThrowIfCancellationRequested(); - writer + getter .Write($"case {i + 1}: return new " + kvpType + "(") .Write(nonExceptionParams[i].Name.Wrap()) .Write(", _") .Write(nonExceptionParams[i].UpperCasedName) - .WriteLine(");"); + .Line(");"); } - writer.WriteLine( - "default: throw new global::System.IndexOutOfRangeException(nameof(index));" - ); + getter + .Write("default: throw new global::System.IndexOutOfRangeException(nameof(index))") + .Line(";"); } } - } - } + }, + null + ); EmitStructEnumerator(writer, structName, kvpType, ienumeratorType, ienumerableType, ienumerableKvpType); } @@ -1024,12 +1015,10 @@ static void EmitStructEnumerator( string ienumerableKvpType ) { - var currentPropertyType = - $"{PurviewTypeLibrary.System.Object.MakeNullable(writer)} global::System.Collections.IEnumerator.Current => Current;"; writer.NewLine(); using ( - writer.WriteStructScope( + writer.StructScope( new TypeDeclarationOptions("Enumerator", TypeDeclarationAccessibility.Public) { IncludeGeneratedAttributes = false, @@ -1038,49 +1027,120 @@ string ienumerableKvpType ) ) { - writer - .Write("readonly ") - .Write(structName) - .WriteLine(" _state;") - .Write("int _index;") - .NewLine() - .Write("public Enumerator(") - .Write(structName) - .WriteLine(" state)"); + writer.Field( + new FieldDeclarationOptions("_state", new TypeReference(new TypeIdentity(structName, null))) + { + IsReadOnly = true, + IncludeGeneratedAttributes = false, + } + ); - using (writer.OpenBlockScope()) - { - writer.Write("_state = state;").NewLine(); - writer.Write("_index = -1;").NewLine(); - } + writer.Field( + new FieldDeclarationOptions("_index", PurviewTypeLibrary.System.Int32.AsTypeReference()) + { + IncludeGeneratedAttributes = false, + } + ); + + writer.NewLine(); + + writer.Constructor( + new ConstructorDeclarationOptions("Enumerator", TypeDeclarationAccessibility.Public) + { + Parameters = + [ + new ParameterDeclarationOptions("state", new TypeReference(new TypeIdentity(structName, null))), + ], + IncludeGeneratedAttributes = false, + }, + ctor => + { + ctor.Assignment("_state", "state").NewLine(); + ctor.Assignment("_index", "-1").NewLine(); + } + ); writer .NewLine() - .Write($"public {kvpType} Current => _state[_index];") + .Property( + new PropertyDeclarationOptions( + "Current", + new TypeReference(new TypeIdentity(kvpType, null)), + TypeDeclarationAccessibility.Public + ) + { + ExpressionBody = "_state[_index]", + IncludeGeneratedAttributes = false, + } + ) .NewLine() .NewLine() - .Write(currentPropertyType) + .Write(PurviewTypeLibrary.System.Object.MakeNullable(writer)) + .Write(" global::System.Collections.IEnumerator.Current") + .Line(" => Current;") .NewLine() .NewLine() - .Write("public bool MoveNext() => ++_index < _state.Count;") + .MethodExpression( + new MethodDeclarationOptions( + "MoveNext", + PurviewTypeLibrary.System.Boolean.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) + { + ExpressionBody = "++_index < _state.Count", + IncludeGeneratedAttributes = false, + } + ) .NewLine() .NewLine() - .Write("public void Reset() => _index = -1;") + .MethodExpression( + new MethodDeclarationOptions( + "Reset", + PurviewTypeLibrary.System.Void.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) + { + ExpressionBody = "_index = -1", + IncludeGeneratedAttributes = false, + } + ) .NewLine() .NewLine() - .Write("public void Dispose() { }"); + .Method( + new MethodDeclarationOptions( + "Dispose", + PurviewTypeLibrary.System.Void.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) + { + IncludeGeneratedAttributes = false, + }, + _ => { } + ); } writer .NewLine() .NewLine() - .Write("public Enumerator GetEnumerator() => new Enumerator(this);") + .MethodExpression( + new MethodDeclarationOptions( + "GetEnumerator", + new TypeReference(new TypeIdentity("Enumerator", null)), + TypeDeclarationAccessibility.Public + ) + { + ExpressionBody = "new Enumerator(this)", + IncludeGeneratedAttributes = false, + } + ) .NewLine() .NewLine() - .Write($"{ienumeratorType} {ienumerableKvpType}.GetEnumerator() => GetEnumerator();") + .Write(ienumeratorType + " " + ienumerableKvpType + ".GetEnumerator() => GetEnumerator()") + .Line(";") .NewLine() .NewLine() - .Write($"{ienumerableType} global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();"); + .Write(ienumerableType + " global::System.Collections.IEnumerable.GetEnumerator() => GetEnumerator()") + .Line(";"); } static void EmitScopeStateStruct(CodeWriter writer, LogMethodTarget methodTarget, SourceProductionContext context) @@ -1099,7 +1159,7 @@ static void EmitScopeStateStruct(CodeWriter writer, LogMethodTarget methodTarget writer.NewLine(); using ( - writer.WriteStructScope( + writer.StructScope( new TypeDeclarationOptions(structName, TypeDeclarationAccessibility.Private) { IsReadOnly = true, @@ -1109,10 +1169,15 @@ static void EmitScopeStateStruct(CodeWriter writer, LogMethodTarget methodTarget ) ) { - writer - .Write("static readonly string s_originalFormat = ") - .Write(methodTarget.MessageTemplate.Wrap()) - .WriteLine(";"); + writer.Field( + new FieldDeclarationOptions("s_originalFormat", PurviewTypeLibrary.System.String.AsTypeReference()) + { + IsStatic = true, + IsReadOnly = true, + Initializer = methodTarget.MessageTemplate.Wrap(), + IncludeGeneratedAttributes = false, + } + ); if (nonExceptionParams.Count > 0) { @@ -1122,37 +1187,37 @@ static void EmitScopeStateStruct(CodeWriter writer, LogMethodTarget methodTarget { context.CancellationToken.ThrowIfCancellationRequested(); - writer - .Write("public readonly ") - .Write(param.ParameterType) - .Write(" _") - .Write(param.UpperCasedName) - .WriteLine(";"); - } - - writer.NewLine().Write("public ").Write(structName).Write('('); - - for (var i = 0; i < nonExceptionParams.Count; i++) - { - context.CancellationToken.ThrowIfCancellationRequested(); - - writer.Write(nonExceptionParams[i].ParameterType).Write(' ').Write(nonExceptionParams[i].Name); - - if (i < nonExceptionParams.Count - 1) - writer.Write(", "); + writer.Field( + new FieldDeclarationOptions($"_{param.UpperCasedName}", param.ParameterType) + { + Accessibility = TypeDeclarationAccessibility.Public, + IsReadOnly = true, + IncludeGeneratedAttributes = false, + } + ); } - writer.Write(")"); + writer.NewLine(); - using (writer.OpenBlockScope()) - { - foreach (var param in nonExceptionParams) + writer.Constructor( + new ConstructorDeclarationOptions(structName, TypeDeclarationAccessibility.Public) { - context.CancellationToken.ThrowIfCancellationRequested(); + Parameters = + [ + .. nonExceptionParams.Select(p => new ParameterDeclarationOptions(p.Name, p.ParameterType)), + ], + IncludeGeneratedAttributes = false, + }, + ctor => + { + foreach (var param in nonExceptionParams) + { + context.CancellationToken.ThrowIfCancellationRequested(); - writer.Write("_").Write(param.UpperCasedName).Write(" = ").Write(param.Name).WriteLine(";"); + ctor.Write("_").Write(param.UpperCasedName).Write(" = ").Write(param.Name).Line(";"); + } } - } + ); } // Lazy ToString() — format is deferred until a provider actually needs the string. @@ -1163,86 +1228,112 @@ static void EmitScopeStateStruct(CodeWriter writer, LogMethodTarget methodTarget [.. methodTarget.Parameters] ); - writer.NewLine().NewLine().Write("public override string ToString()"); - - using (writer.OpenBlockScope()) - { - writer - .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"); - } - writer .NewLine() .NewLine() - .Write("public int Count => ") - .Write(count.ToString(CultureInfo.InvariantCulture)) - .WriteLine(";") - .NewLine() - .Write($"public {kvpType} this[int index]"); - - using (writer.OpenBlockScope()) - { - if (writer.IsNullableContextEnabled is null or true) - { - writer.WriteLine("get => index switch {"); - writer.Indent(); - writer.WriteLine("0 => new(\"{OriginalFormat}\", s_originalFormat),"); + .Method( + new MethodDeclarationOptions( + "ToString", + PurviewTypeLibrary.System.String.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) + { + IsOverride = true, + IncludeGeneratedAttributes = false, + }, + body => + body.HashDefines( + "NET", + hashWriter => + hashWriter + .Write( + "return string.Create(global::System.Globalization.CultureInfo.InvariantCulture, $" + ) + .Write(interpolatedMessage.Wrap()) + .Line(");") + .HashElse() + .Write("return global::System.FormattableString.Invariant($") + .Write(interpolatedMessage.Wrap()) + .Line(");") + ) + ); - for (var i = 0; i < nonExceptionParams.Count; i++) + writer + .NewLine() + .NewLine() + .Property( + new PropertyDeclarationOptions( + "Count", + PurviewTypeLibrary.System.Int32.AsTypeReference(), + TypeDeclarationAccessibility.Public + ) { - context.CancellationToken.ThrowIfCancellationRequested(); - - writer - .Write($"{i + 1} => new(") - .Write(nonExceptionParams[i].Name.Wrap()) - .Write(", _") - .Write(nonExceptionParams[i].UpperCasedName) - .WriteLine("),"); + ExpressionBody = count.ToString(CultureInfo.InvariantCulture), + IncludeGeneratedAttributes = false, } + ) + .NewLine(); - writer.WriteLine("_ => throw new global::System.IndexOutOfRangeException(nameof(index))"); - writer.Unindent(); - writer.WriteLine("};"); - } - else + writer.Indexer( + new IndexerDeclarationOptions( + new TypeReference(new TypeIdentity(kvpType, null)), + new ParameterDeclarationOptions("index", PurviewTypeLibrary.System.Int32.AsTypeReference()) + ) { - writer.Write("get"); - using (writer.OpenBlockScope()) + Accessibility = TypeDeclarationAccessibility.Public, + IncludeGeneratedAttributes = false, + }, + getter => + { + if (writer.IsNullableContextEnabled is null or true) { - writer.Write("switch (index)"); - using (writer.OpenBlockScope()) + getter.Line("return index switch {"); + getter.Indent(); + getter.Line("0 => new(\"{OriginalFormat}\", s_originalFormat),"); + + for (var i = 0; i < nonExceptionParams.Count; i++) { - writer.WriteLine( - "case 0: return new " + kvpType + "(\"{OriginalFormat}\", s_originalFormat);" - ); + context.CancellationToken.ThrowIfCancellationRequested(); + + getter + .Write($"{i + 1} => new(") + .Write(nonExceptionParams[i].Name.Wrap()) + .Write(", _") + .Write(nonExceptionParams[i].UpperCasedName) + .Line("),"); + } + + getter.Line("_ => throw new global::System.IndexOutOfRangeException(nameof(index))"); + getter.Unindent(); + getter.Line("};"); + } + else + { + getter.Write("switch (index)"); + using (getter.OpenBlockScope()) + { + getter.Line("case 0: return new " + kvpType + "(\"{OriginalFormat}\", s_originalFormat);"); for (var i = 0; i < nonExceptionParams.Count; i++) { context.CancellationToken.ThrowIfCancellationRequested(); - writer + getter .Write($"case {i + 1}: return new " + kvpType + "(") .Write(nonExceptionParams[i].Name.Wrap()) .Write(", _") .Write(nonExceptionParams[i].UpperCasedName) - .WriteLine(");"); + .Line(");"); } - writer.WriteLine( - "default: throw new global::System.IndexOutOfRangeException(nameof(index));" - ); + getter + .Write("default: throw new global::System.IndexOutOfRangeException(nameof(index))") + .Line(";"); } } - } - } + }, + null + ); EmitStructEnumerator(writer, structName, kvpType, ienumeratorType, ienumerableType, ienumerableKvpType); } @@ -1259,22 +1350,27 @@ SourceProductionContext context { 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.MakeNullable(writer)); - else - writer.Write(PurviewTypeLibrary.System.Void); - - writer.Write(' ').Write(methodTarget.MethodName).Write('('); - - EmitParametersAsMethodArgumentList(methodTarget, writer, context); + var returnType = methodTarget.IsScoped + ? TypeLibrary.System.IDisposable.MakeNullable(writer) + : PurviewTypeLibrary.System.Void.AsTypeReference(); - writer.Write(")"); + writer.NewLine(); - using (writer.OpenBlockScope()) + using ( + writer.MethodScope( + new MethodDeclarationOptions(methodTarget.MethodName, returnType, TypeDeclarationAccessibility.Public) + { + Parameters = + [ + .. methodTarget.Parameters.Select(p => new ParameterDeclarationOptions( + p.Name, + p.ParameterType + )), + ], + IncludeGeneratedAttributes = false, + } + ) + ) { // Call the private Logging method if (methodTarget.IsScoped) @@ -1333,7 +1429,7 @@ SourceProductionContext context // Return if scoped if (methodTarget.IsScoped) { - writer.NewLine().Write("return loggingResult;"); + writer.NewLine().Return("loggingResult"); } } diff --git a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.cs b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.cs index 07e90dfd..4c5320ae 100644 --- a/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/LoggerGenTargetClassEmitter.cs @@ -13,7 +13,7 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd var writer = output.CreateWriter(); - using (writer.WriteBlockNamespaceScope(target.ClassNamespace)) + using (writer.BlockNamespaceScope(target.ClassNamespace)) { List parentScopes = []; if (target.TelemetryGeneration.TelemetryNamesNamespace == null) @@ -21,7 +21,7 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd foreach (var parent in target.ParentClasses) { parentScopes.Add( - writer.WriteClassScope( + writer.ClassScope( new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } ) ); @@ -81,14 +81,16 @@ static void EmitFields(LoggerOutputContext output, CodeWriter writer, SourceProd context.CancellationToken.ThrowIfCancellationRequested(); writer - .Write("readonly ") - .Write(TypeLibrary.Logging.MicrosoftExtensions.ILogger) - .Write('<') - .Write(target.InterfaceType) - .Write('>') - .Write(' ') - .Write(PropertyLibrary.Logging.LoggerFieldName) - .Write(';') + .Field( + new FieldDeclarationOptions( + PropertyLibrary.Logging.LoggerFieldName, + TypeLibrary.Logging.MicrosoftExtensions.ILogger.MakeGeneric(target.InterfaceType).AsTypeReference() + ) + { + IsReadOnly = true, + IncludeGeneratedAttributes = false, + } + ) .NewLine(); foreach (var methodTarget in target.LogMethods) diff --git a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Fields.cs b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Fields.cs index 16a6a459..c83f567c 100644 --- a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Fields.cs +++ b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Fields.cs @@ -14,14 +14,16 @@ static void EmitFields(LoggerOutputContext output, CodeWriter writer, SourceProd context.CancellationToken.ThrowIfCancellationRequested(); writer - .Write("readonly ") - .Write(TypeLibrary.Logging.MicrosoftExtensions.ILogger) - .Write('<') - .Write(target.InterfaceType) - .Write('>') - .Write(' ') - .Write(PropertyLibrary.Logging.LoggerFieldName) - .Write(';') + .Field( + new FieldDeclarationOptions( + PropertyLibrary.Logging.LoggerFieldName, + TypeLibrary.Logging.MicrosoftExtensions.ILogger.MakeGeneric(target.InterfaceType).AsTypeReference() + ) + { + IsReadOnly = true, + IncludeGeneratedAttributes = false, + } + ) .NewLine() .NewLine(); @@ -85,70 +87,58 @@ static void EmitFields(LoggerOutputContext output, CodeWriter writer, SourceProd internal static void EmitLogActionField(CodeWriter writer, LogMethodTarget methodTarget) { - writer - .Write("static readonly ") - .Write(methodTarget.IsScoped ? PurviewTypeLibrary.System.Func : PurviewTypeLibrary.System.Action) - .Write('<') - .Write(TypeLibrary.Logging.MicrosoftExtensions.ILogger) - .Write(", "); - - foreach (var parameter in methodTarget.ParametersSansException) - writer.Write(parameter.ParameterType).Write(", "); - - if (methodTarget.IsScoped) - { - writer.Write(TypeLibrary.System.IDisposable.MakeNullable(writer)); - writer.Write("> "); - } - else - { - writer.Write(TypeLibrary.System.Exception.MakeNullable(writer)); - writer.Write("> "); - } - - writer - .Write(methodTarget.LoggerActionFieldName) - .Write(" = ") - .Write(TypeLibrary.Logging.MicrosoftExtensions.LoggerMessage) - .Write(".Define"); - - if (methodTarget.IsScoped) - writer.Write("Scope"); - - if (methodTarget.ParameterCountSansException > 0) - { - writer.Write('<'); - - var i = 0; - foreach (var parameter in methodTarget.ParametersSansException) + var useNullable = writer.IsNullableContextEnabled is null or true; + + var typeName = + (methodTarget.IsScoped ? "global::System.Func<" : "global::System.Action<") + + TypeLibrary.Logging.MicrosoftExtensions.ILogger.RenderFullNameForNullable(useNullable) + + string.Concat( + methodTarget.ParametersSansException.Select(p => + ", " + p.ParameterType.RenderFullNameForNullable(useNullable) + ) + ) + + ", " + + ( + methodTarget.IsScoped + ? TypeLibrary.System.IDisposable.MakeNullable(writer).RenderFullNameForNullable(useNullable) + : TypeLibrary.System.Exception.MakeNullable(writer).RenderFullNameForNullable(useNullable) + ) + + ">"; + + var genericArguments = + methodTarget.ParameterCountSansException > 0 + ? "<" + + string.Join( + ", ", + methodTarget.ParametersSansException.Select(p => + p.ParameterType.RenderFullNameForNullable(useNullable) + ) + ) + + ">" + : ""; + + var eventId = methodTarget.EventId ?? SharedHelpers.GetNonRandomizedHashCode(methodTarget.MethodName); + var arguments = methodTarget.IsScoped + ? $"\"{methodTarget.MessageTemplate}\"" + : $"{methodTarget.MSLevel}, new global::Microsoft.Extensions.Logging.EventId({eventId.ToString(CultureInfo.InvariantCulture)}, \"{methodTarget.LogName}\"), \"{methodTarget.MessageTemplate}\""; + + var initializer = + $"global::Microsoft.Extensions.Logging.LoggerMessage.Define" + + (methodTarget.IsScoped ? "Scope" : "") + + genericArguments + + $"({arguments})"; + + writer.Field( + new FieldDeclarationOptions( + methodTarget.LoggerActionFieldName, + new TypeReference(new TypeIdentity(typeName, null)) + ) { - writer.Write(parameter.ParameterType); - if (i < methodTarget.ParameterCountSansException - 1) - writer.Write(", "); - - i++; + IsStatic = true, + IsReadOnly = true, + Initializer = initializer, + IncludeGeneratedAttributes = false, } - - writer.Write('>'); - } - - writer.Write('('); - - if (!methodTarget.IsScoped) - { - writer.Write(methodTarget.MSLevel).Write(", "); - - var eventId = methodTarget.EventId ?? SharedHelpers.GetNonRandomizedHashCode(methodTarget.MethodName); - writer - .Write("new ") - .Write(TypeLibrary.Logging.MicrosoftExtensions.EventId) - .Write('(') - .Write(eventId.ToString(CultureInfo.InvariantCulture)) - .Write(", \"") - .Write(methodTarget.LogName) - .Write("\"), "); - } - - writer.Write('"').Write(methodTarget.MessageTemplate).Write('"').Write(");").NewLine(); + ); } } diff --git a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Methods.cs index 16c932c8..6d8f72cf 100644 --- a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.Methods.cs @@ -14,7 +14,7 @@ internal static void EmitThrowStub(CodeWriter writer, LogMethodTarget methodTarg writer.NewLine(); using ( - writer.WriteMethodScope( + writer.MethodScope( new(methodTarget.MethodName, returnType, TypeDeclarationAccessibility.Public) { Parameters = @@ -114,7 +114,7 @@ SourceProductionContext context writer.NewLine(); using ( - writer.WriteMethodScope( + writer.MethodScope( new MethodDeclarationOptions( methodName, returnType, @@ -143,15 +143,10 @@ SourceProductionContext context } else { - writer - .Write("if (!") - .Write(PropertyLibrary.Logging.LoggerFieldName) - .Write(".IsEnabled(") - .Write(methodTarget.MSLevel) - .WriteLine("))"); - - using (writer.OpenBlockScope()) - writer.WriteLine("return;"); + writer.IfBlock( + "!" + PropertyLibrary.Logging.LoggerFieldName + ".IsEnabled(" + methodTarget.MSLevel + ")", + static body => body.Return() + ); writer .NewLine() @@ -202,7 +197,7 @@ SourceProductionContext context writer.NewLine(); using ( - writer.WriteMethodScope( + writer.MethodScope( new MethodDeclarationOptions(methodTarget.MethodName, returnType, TypeDeclarationAccessibility.Public) { Parameters = @@ -274,7 +269,7 @@ SourceProductionContext context // Return if scoped if (methodTarget.IsScoped) { - writer.NewLine().Write("return loggingResult;"); + writer.NewLine().Return("loggingResult"); } } diff --git a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.cs b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.cs index b49c315c..b29e20c7 100644 --- a/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/LoggerTargetClassEmitter.cs @@ -11,7 +11,7 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd output.Context.Debug($"Generating logging class for: {target.FullyQualifiedName}"); var writer = output.CreateWriter(); - using (writer.WriteBlockNamespaceScope(target.ClassNamespace)) + using (writer.BlockNamespaceScope(target.ClassNamespace)) { List parentScopes = []; if (target.TelemetryGeneration.TelemetryNamesNamespace == null) @@ -19,7 +19,7 @@ public static void GenerateImplementation(LoggerOutputContext output, SourceProd foreach (var parent in target.ParentClasses) { parentScopes.Add( - writer.WriteClassScope( + writer.ClassScope( new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } ) ); diff --git a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Fields.cs b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Fields.cs index 39156f24..3b13d4ea 100644 --- a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Fields.cs +++ b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Fields.cs @@ -23,20 +23,31 @@ static void EmitFields( if (readonlyFields) { writer - .Write("readonly ") - .Write((string)TypeLibrary.Metrics.SystemDiagnostics.Meter) - .Write(' ') - .Write(MeterFieldName) - .WriteLine(";") + .Field( + new FieldDeclarationOptions( + MeterFieldName, + TypeLibrary.Metrics.SystemDiagnostics.Meter.AsTypeReference() + ) + { + IsReadOnly = true, + IncludeGeneratedAttributes = false, + } + ) .NewLine(); } else { writer - .Write(TypeLibrary.Metrics.SystemDiagnostics.Meter) - .Write(' ') - .Write(MeterFieldName) - .WriteLine(writer.IsNullableContextEnabled is null or true ? " = default!;" : " = default;") + .Field( + new FieldDeclarationOptions( + MeterFieldName, + TypeLibrary.Metrics.SystemDiagnostics.Meter.AsTypeReference() + ) + { + Initializer = writer.IsNullableContextEnabled is null or true ? "default!" : "default", + IncludeGeneratedAttributes = false, + } + ) .NewLine(); } @@ -64,15 +75,23 @@ static void EmitFields( if (emitReadonly) { - writer.Write("readonly ").Write((string)type).Write(' ').Write(method.FieldName).WriteLine(";"); + writer.Field( + new FieldDeclarationOptions(method.FieldName, type) + { + IsReadOnly = true, + IncludeGeneratedAttributes = false, + } + ); } else { - writer - .Write(type) - .Write(' ') - .Write(method.FieldName) - .WriteLine(writer.IsNullableContextEnabled is null or true ? " = default!;" : " = default;"); + writer.Field( + new FieldDeclarationOptions(method.FieldName, type) + { + Initializer = writer.IsNullableContextEnabled is null or true ? "default!" : "default", + IncludeGeneratedAttributes = false, + } + ); } } } diff --git a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.InitializationMethod.cs b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.InitializationMethod.cs index 89fc6efa..efddda15 100644 --- a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.InitializationMethod.cs +++ b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.InitializationMethod.cs @@ -12,31 +12,38 @@ static void EmitInitializationMethod(MeterOutputContext output, CodeWriter write context.CancellationToken.ThrowIfCancellationRequested(); - writer.NewLine().Write("void ").Write(PropertyLibrary.Metrics.MeterInitializationMethod).Write('('); - - if (supportsIMeterFactory) - { - writer - .Write(TypeLibrary.Metrics.SystemDiagnostics.IMeterFactory) - .Write(' ') - .Write(PropertyLibrary.Metrics.MeterFactoryParameterName); - } - - writer.Write(")"); - - using (writer.OpenBlockScope()) + writer.NewLine(); + + using ( + writer.MethodScope( + new MethodDeclarationOptions( + PropertyLibrary.Metrics.MeterInitializationMethod, + PurviewTypeLibrary.System.Void.AsTypeReference() + ) + { + Parameters = supportsIMeterFactory + ? + [ + new ParameterDeclarationOptions( + PropertyLibrary.Metrics.MeterFactoryParameterName, + TypeLibrary.Metrics.SystemDiagnostics.IMeterFactory.AsTypeReference() + ), + ] + : [], + IncludeGeneratedAttributes = false, + } + ) + ) { // Double-init guard: prevents re-initialization when the method path is used // (occurs in Logging+Metrics multi-target where Logging owns the constructor). - writer.Write("if (").Write(MeterFieldName).WriteLine(" != null)"); - - using (writer.OpenBlockScope()) - { - writer - .Write("throw new ") - .Write(TypeLibrary.System.Exception) - .WriteLine("(\"The meters have already been initialized.\");"); - } + writer.IfBlock( + MeterFieldName + " != null", + body => + body.Throw( + "new " + TypeLibrary.System.Exception + "(\"The meters have already been initialized.\")" + ) + ); writer.NewLine(); @@ -54,19 +61,25 @@ static void EmitInlineConstructor(MeterOutputContext output, CodeWriter writer, context.CancellationToken.ThrowIfCancellationRequested(); - writer.NewLine().Write("public ").Write(target.ClassNameToGenerate).Write('('); - - if (supportsIMeterFactory) - { - writer - .Write(TypeLibrary.Metrics.SystemDiagnostics.IMeterFactory) - .Write(' ') - .Write(PropertyLibrary.Metrics.MeterFactoryParameterName); - } - - writer.Write(")"); - - using (writer.OpenBlockScope()) + writer.NewLine(); + + using ( + writer.ConstructorScope( + new ConstructorDeclarationOptions(target.ClassNameToGenerate, TypeDeclarationAccessibility.Public) + { + Parameters = supportsIMeterFactory + ? + [ + new ParameterDeclarationOptions( + PropertyLibrary.Metrics.MeterFactoryParameterName, + TypeLibrary.Metrics.SystemDiagnostics.IMeterFactory.AsTypeReference() + ), + ] + : [], + IncludeGeneratedAttributes = false, + } + ) + ) { EmitInitializationBodyContent(output, writer); } @@ -79,16 +92,9 @@ static void EmitInitializationBodyContent(MeterOutputContext output, CodeWriter const string meterTagsVariableName = "meterTags"; var dictType = GetDictionaryType(writer); - writer - .Write((string)dictType) - .Write(' ') - .Write(meterTagsVariableName) - .Write(" = new ") - .Write((string)dictType) - .WriteLine("();") - .NewLine(); + writer.Assignment((string)dictType, meterTagsVariableName, "new " + (string)dictType + "()").NewLine(); - writer.Write(PartialMeterTagsMethod).Write('(').Write(meterTagsVariableName).WriteLine(");").NewLine(); + writer.Write(PartialMeterTagsMethod).Write('(').Write(meterTagsVariableName).Line(");").NewLine(); if (supportsIMeterFactory) { @@ -103,8 +109,8 @@ static void EmitInitializationBodyContent(MeterOutputContext output, CodeWriter .Write(") {") .NewLine(); writer.Indent(); - writer.WriteLine("Version = null,"); - writer.WriteLine($"Tags = {meterTagsVariableName}"); + writer.Line("Version = null,"); + writer.Line($"Tags = {meterTagsVariableName}"); writer.Unindent(); writer.Write("});").NewLine(); } @@ -116,7 +122,7 @@ static void EmitInitializationBodyContent(MeterOutputContext output, CodeWriter .Write(TypeLibrary.Metrics.SystemDiagnostics.Meter) .Write('(') .Write(target.MeterName!.Wrap()) - .WriteLine(");") + .Line(");") .NewLine(); } @@ -137,17 +143,12 @@ static void EmitInitialiseInstrumentVariable(InstrumentTarget method, CodeWriter var dictType = GetDictionaryType(writer); writer - .Write((string)dictType) - .Write(' ') - .Write(tagVariableName) - .Write(" = new ") - .Write((string)dictType) - .WriteLine("();") + .Assignment((string)dictType, tagVariableName, "new " + (string)dictType + "()") .NewLine() .Write(method.TagPopulateMethodName) .Write('(') .Write(tagVariableName) - .WriteLine(");") + .Line(");") .NewLine(); writer @@ -166,7 +167,7 @@ static void EmitInitialiseInstrumentVariable(InstrumentTarget method, CodeWriter .Write(description) .Write(", tags: ") .Write(tagVariableName) - .WriteLine(");"); + .Line(");"); } } } diff --git a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Methods.cs b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Methods.cs index 0f753732..69114ad6 100644 --- a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Methods.cs +++ b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.Methods.cs @@ -9,21 +9,33 @@ partial class MeterTargetClassEmitter { static void EmitThrowStub(CodeWriter writer, InstrumentTarget methodTarget) { - writer.NewLine().Write("public ").Write(methodTarget.ReturnType); - - writer.Write(' ').Write(methodTarget.MethodName).Write('('); + writer.NewLine(); - for (var i = 0; i < methodTarget.Parameters.Count; i++) + using ( + writer.MethodScope( + new MethodDeclarationOptions( + methodTarget.MethodName, + methodTarget.ReturnType, + TypeDeclarationAccessibility.Public + ) + { + Parameters = + [ + .. methodTarget.Parameters.Select(p => new ParameterDeclarationOptions( + p.ParameterName, + p.ParameterType + )), + ], + ExpressionBody = "throw new global::System.NotSupportedException()", + IncludeGeneratedAttributes = false, + } + ) + ) { - if (i > 0) - writer.Write(", "); - writer - .Write(methodTarget.Parameters[i].ParameterType) - .Write(' ') - .Write(methodTarget.Parameters[i].ParameterName); + // } - writer.Write(") => throw new global::System.NotSupportedException();").NewLine(); + writer.NewLine(); } static void EmitMethods(MeterOutputContext output, CodeWriter writer, SourceProductionContext context) @@ -73,11 +85,12 @@ static void EmitPartialMethods(MeterOutputContext output, CodeWriter writer, Sou var dictType = GetDictionaryType(writer); writer .NewLine() - .Write("partial void ") + .Write("partial") + .Write(" void ") .Write(PartialMeterTagsMethod) .Write('(') .Write(dictType) - .WriteLine(" meterTags);") + .Line(" meterTags);") .NewLine(); foreach (var instrument in target.InstrumentationMethods) @@ -89,11 +102,12 @@ static void EmitPartialMethods(MeterOutputContext output, CodeWriter writer, Sou continue; writer - .Write("partial void ") + .Write("partial") + .Write(" void ") .Write(instrument.TagPopulateMethodName) .Write('(') .Write(dictType) - .WriteLine(" instrumentTags);") + .Line(" instrumentTags);") .NewLine(); } } @@ -132,7 +146,7 @@ SourceProductionContext context writer.NewLine(); using ( - writer.WriteMethodScope( + writer.MethodScope( new MethodDeclarationOptions( methodName, returnType, @@ -212,29 +226,30 @@ .. methodTarget.Parameters.Select(p => static void EmitObservableInstrumentBodyTest(CodeWriter writer, InstrumentTarget method) { - writer.Write("if (").Write(method.FieldName).WriteLine(" != null)"); - - using (writer.OpenBlockScope()) - { - if (method.InstrumentAttribute?.ThrowOnAlreadyInitialized == true) - { - writer - .Write("throw new ") - .Write(TypeLibrary.System.Exception) - .Write("(\"") - .Write(method.MetricName) - .WriteLine(" has already been initialized.\");"); - } - else + writer.IfBlock( + method.FieldName + " != null", + body => { - writer.Write("return"); - - if (method.ReturnsBool) - writer.WriteLine(" false;"); + if (method.InstrumentAttribute?.ThrowOnAlreadyInitialized == true) + { + writer + .Write("throw new ") + .Write(TypeLibrary.System.Exception) + .Write("(\"") + .Write(method.MetricName) + .Line(" has already been initialized.\");"); + } else - writer.Write(";").NewLine(); + { + writer.Write("return"); + + if (method.ReturnsBool) + writer.Line(" false;"); + else + writer.Write(";").NewLine(); + } } - } + ); writer.NewLine(); } @@ -263,14 +278,14 @@ static void EmitObservableInstrumentBody(CodeWriter writer, InstrumentTarget met if (tagVariableName != null) { - writer.WriteLine().Write(", tags: ").WriteLine(tagVariableName); + writer.NewLine().Write(", tags: ").Line(tagVariableName); } - writer.WriteLine(");"); + writer.Line(");"); if (method.ReturnsBool) { - writer.NewLine().Write("return true;"); + writer.NewLine().Return("true"); } } @@ -335,7 +350,7 @@ static void EmitInstrumentBody( if (methodTarget.ReturnsBool) { - writer.NewLine().Write("return true;"); + writer.NewLine().Return("true"); } } @@ -358,39 +373,20 @@ static void EmitInstrumentBody( } var tagVariableName = Utilities.LowercaseFirstChar(methodTarget.MethodName + "TagList"); - writer - .Write(TypeLibrary.System.TagList) - .Write(' ') - .Write(tagVariableName) - .Write(" = new") - .WriteLine("();") - .NewLine(); + writer.Assignment(TypeLibrary.System.TagList, tagVariableName, "new()").NewLine(); foreach (var param in methodTarget.Tags) { if (param.SkipOnNullOrEmpty) { - writer.Write("if (").Write(param.ParameterName).WriteLine(" != default)"); - using (writer.OpenBlockScope()) - { - writer - .Write(tagVariableName) - .Write(".Add(") - .Write(param.GeneratedName.Wrap()) - .Write(", ") - .Write(param.ParameterName) - .WriteLine(");"); - } + writer.IfBlock( + param.ParameterName + " != default", + body => body.MethodCallOn(tagVariableName, "Add", param.GeneratedName.Wrap(), param.ParameterName) + ); } else { - writer - .Write(tagVariableName) - .Write(".Add(") - .Write(param.GeneratedName.Wrap()) - .Write(", ") - .Write(param.ParameterName) - .WriteLine(");"); + writer.MethodCallOn(tagVariableName, "Add", param.GeneratedName.Wrap(), param.ParameterName); } } diff --git a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.cs b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.cs index 20b93053..e0a339ee 100644 --- a/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.cs +++ b/src/src/SourceGenerator/Emitters/MeterTargetClassEmitter.cs @@ -21,7 +21,7 @@ public static void GenerateImplementation(MeterOutputContext output, SourceProdu output.Context.Debug($"Generating metric class for: {target.FullyQualifiedName}"); var writer = output.CreateWriter(); - using (writer.WriteBlockNamespaceScope(target.ClassNamespace)) + using (writer.BlockNamespaceScope(target.ClassNamespace)) { List parentScopes = []; if (target.TelemetryGeneration.TelemetryNamesNamespace == null) @@ -29,7 +29,7 @@ public static void GenerateImplementation(MeterOutputContext output, SourceProdu foreach (var parent in target.ParentClasses) { parentScopes.Add( - writer.WriteClassScope( + writer.ClassScope( new TypeDeclarationOptions(parent) { IsSealed = false, IncludeGeneratedAttributes = false } ) ); diff --git a/src/src/SourceGenerator/Emitters/TelemetryNamesEmitter.cs b/src/src/SourceGenerator/Emitters/TelemetryNamesEmitter.cs index d3732ea3..02f5b558 100644 --- a/src/src/SourceGenerator/Emitters/TelemetryNamesEmitter.cs +++ b/src/src/SourceGenerator/Emitters/TelemetryNamesEmitter.cs @@ -21,10 +21,11 @@ GenerationContext generationContext var writer = output.CreateWriter(); var hasNamespace = !string.IsNullOrWhiteSpace(rootNamespace); - using (writer.WriteBlockNamespaceScope(rootNamespace)) + using (writer.BlockNamespaceScope(rootNamespace)) { + writer.XmlSummary("Contains the names of the meters and activity sources generated for the assembly."); using ( - writer.WriteClassScope( + writer.ClassScope( new(className) { IsStatic = true, @@ -35,7 +36,8 @@ GenerationContext generationContext ) { var stringArrayType = PurviewTypeLibrary.System.String.AsTypeReference().MakeArray(); - writer.WriteField( + writer.XmlSummary("Gets the names of the meters generated for the assembly."); + writer.Field( new("MeterNames", stringArrayType, TypeDeclarationAccessibility.Public) { IsStatic = true, @@ -45,7 +47,8 @@ GenerationContext generationContext } ); - writer.WriteField( + writer.XmlSummary("Gets the names of the activity sources generated for the assembly."); + writer.Field( new("ActivitySourceNames", stringArrayType, TypeDeclarationAccessibility.Public) { IsStatic = true, diff --git a/src/src/SourceGenerator/Helpers/TypeLibrary.cs b/src/src/SourceGenerator/Helpers/TypeLibrary.cs index 233be40b..4124f34e 100644 --- a/src/src/SourceGenerator/Helpers/TypeLibrary.cs +++ b/src/src/SourceGenerator/Helpers/TypeLibrary.cs @@ -338,12 +338,4 @@ public static ImmutableArray GetGeneratedTypes() => 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 7135ad90..d0a6c234 100644 --- a/src/src/SourceGenerator/Helpers/Utilities.cs +++ b/src/src/SourceGenerator/Helpers/Utilities.cs @@ -76,6 +76,9 @@ GenerationType requestedType if (attribute.AttributeClass == null) continue; + if (attribute.AttributeClass.TypeKind == TypeKind.Error) + continue; + var attributeType = TypeReference.Create(attribute.AttributeClass); if (IsActivityAttribute(attributeType)) diff --git a/src/src/SourceGenerator/Records/OutputContexts.cs b/src/src/SourceGenerator/Records/OutputContexts.cs index c70c148b..9f132eda 100644 --- a/src/src/SourceGenerator/Records/OutputContexts.cs +++ b/src/src/SourceGenerator/Records/OutputContexts.cs @@ -10,17 +10,17 @@ readonly record struct ActivityOutputContext( GenerationContext Context ) { - public CodeWriter CreateWriter() => Context.CreateCodeWriter().WriteAutoGeneratedHeader(); + public CodeWriter CreateWriter() => Context.CreateCodeWriter().AutoGeneratedHeader(); } readonly record struct LoggerOutputContext(LoggerTarget Target, GenerationContext Context) { - public CodeWriter CreateWriter() => Context.CreateCodeWriter().WriteAutoGeneratedHeader(); + public CodeWriter CreateWriter() => Context.CreateCodeWriter().AutoGeneratedHeader(); } readonly record struct MeterOutputContext(MeterTarget Target, GenerationContext Context) { - public CodeWriter CreateWriter() => Context.CreateCodeWriter().WriteAutoGeneratedHeader(); + public CodeWriter CreateWriter() => Context.CreateCodeWriter().AutoGeneratedHeader(); } readonly record struct TelemetryNamesOutputContext( @@ -30,5 +30,5 @@ readonly record struct TelemetryNamesOutputContext( GenerationContext Context ) { - public CodeWriter CreateWriter() => Context.CreateCodeWriter().WriteAutoGeneratedHeader(); + public CodeWriter CreateWriter() => Context.CreateCodeWriter().AutoGeneratedHeader(); } diff --git a/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs b/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs index 5f8801a7..c650649a 100644 --- a/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs +++ b/src/tests/SourceGenerator.IntegrationTests/Infra/TestHelpers.cs @@ -10,6 +10,7 @@ public static string ReplaceOrdinal(this string value, string oldValue, string n value.Replace(oldValue, newValue, StringComparison.Ordinal); #endif + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0057:Use range operator")] public static List GetCasePermutations(string input) { List result = [];