From 01c9fe3ecfbacdca62148278e63bb9ed99bc5a04 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 15:53:20 +0000 Subject: [PATCH 01/20] Add missing parameter to Kiota engine constructor --- .../KiotaEngine.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.cs b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.cs index d384f22..d513be6 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Diagnostics.CodeAnalysis; namespace Riverside.CompilerPlatform.Features.Swagger; @@ -128,6 +128,7 @@ public partial class KiotaEngine /// The target programming language for code generation. /// The name of the root class to be generated. Can be null to use a default class name. /// The access modifier to apply to generated types. Can be null to use the default accessibility. + /// The namespace for the generated client class. Can be null to use the default namespace. /// The log level to use for diagnostic output during generation. Can be null to use the default log level. /// Indicates whether to use a backing store for generated models. If null, the default behavior is used. /// Indicates whether to exclude backward compatible code from the output. If null, the default behavior is used. @@ -141,7 +142,8 @@ public partial class KiotaEngine /// An array of validation rules to disable during generation. Can be null to enable all rules. /// Indicates whether to clear the internal cache before generation. If null, the default behavior is used. /// Indicates whether to disable SSL validation for network operations. If null, the default behavior is used. - public KiotaEngine(string? d, string? a, string? o, GenerationLanguage l, string? c, Accessibility? tam, ConsoleLogLevel? ll, bool? b, bool? ebc, bool? ad, string[]? s, string[]? ds, bool? co, string[]? m, string[]? i, string[]? e, ValidationRules[]? dvr, bool? cc, bool? dsv) + [SetsRequiredMembers] + public KiotaEngine(string? d, string? a, string? o, GenerationLanguage l, string? c, Accessibility? tam, string? n, ConsoleLogLevel? ll, bool? b, bool? ebc, bool? ad, string[]? s, string[]? ds, bool? co, string[]? m, string[]? i, string[]? e, ValidationRules[]? dvr, bool? cc, bool? dsv) { Path = d; Manifest = a; @@ -149,6 +151,7 @@ public KiotaEngine(string? d, string? a, string? o, GenerationLanguage l, string Language = l; ClassName = c; TypeAccessModifier = tam; + NamespaceName = n; LogLevel = ll; BackingStore = b; ExcludeBackwardCompatible = ebc; From 4dbd47b5926fa10c1faa3ca24d6f47274f14a237 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:45:32 +0000 Subject: [PATCH 02/20] Add .NET tool helpers --- .../Helpers/NETCoreToolHelpers.cs | 74 +++++++++++++++++++ .../Helpers/ProcessHelpers.cs | 13 +++- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/NETCoreToolHelpers.cs diff --git a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/NETCoreToolHelpers.cs b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/NETCoreToolHelpers.cs new file mode 100644 index 0000000..ea1c479 --- /dev/null +++ b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/NETCoreToolHelpers.cs @@ -0,0 +1,74 @@ +using System; +using System.IO; +using System.Threading.Tasks; + +namespace Riverside.CompilerPlatform.Helpers; + +/// +/// Provides helpers for installing and locating .NET tools installed to a specific tool-path directory. +/// +public static class NETCoreToolHelpers +{ + /// + /// Returns the full path to the tool executable expected in . + /// Appends .exe on Windows; uses the bare name on all other platforms. + /// + /// The directory the tool was installed into via --tool-path. + /// The tool executable name (e.g. kiota). + /// The full path including the platform-appropriate extension. + public static string GetExecutablePath(string toolDirectory, string toolName) + => Path.Combine( + toolDirectory, + Environment.OSVersion.Platform == PlatformID.Win32NT ? toolName + ".exe" : toolName); + + /// + /// Ensures the specified .NET tool is available in . + /// + /// + /// + /// If the executable already exists and no specific is requested, the tool is reused immediately. + /// Otherwise dotnet tool install is attempted. If it fails because the tool is already installed, dotnet tool update is tried instead. + /// Installation succeeds when the executable is present after the above steps. + /// + /// + /// The NuGet package ID of the tool (e.g. Riverside.JsonBinder.Console). + /// The directory to install the tool into, passed to --tool-path. + /// + /// A specific version to pin. Pass to install or keep the latest. + /// + /// Maximum wait time per install or update process. Defaults to 5 minutes. + /// + /// A tuple where Success is when the executable is available, and Error carries the captured stderr when installation fails. + /// + public static async Task<(bool Success, string? Error)> EnsureToolAsync( + string toolName, + string toolDirectory, + string? version = null, + TimeSpan? timeout = null) + { + var exe = GetExecutablePath(toolDirectory, toolName); + var effectiveTimeout = timeout ?? TimeSpan.FromMinutes(5); + + Directory.CreateDirectory(toolDirectory); + + if (File.Exists(exe) && string.IsNullOrWhiteSpace(version)) + return (true, null); + + var toolPathArg = $"--tool-path \"{toolDirectory}\""; + var versionArg = string.IsNullOrWhiteSpace(version) ? string.Empty : $" --version {version}"; + + var installResult = await ProcessHelpers.RunNETCoreCliAsync( + $"tool install {toolName} {toolPathArg}{versionArg}", effectiveTimeout); + + if (installResult.ExitCode == 0) + return (true, null); + + // install exits non-zero when the tool is already present; attempt an update instead + var updateResult = await ProcessHelpers.RunNETCoreCliAsync( + $"tool update {toolName} {toolPathArg}{versionArg}", effectiveTimeout); + + return File.Exists(exe) + ? (true, null) + : (false, updateResult.StandardError); + } +} diff --git a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/ProcessHelpers.cs b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/ProcessHelpers.cs index 4132700..9bb2dd5 100644 --- a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/ProcessHelpers.cs +++ b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/ProcessHelpers.cs @@ -49,8 +49,7 @@ public class ProcessOutput(int code, string stdout, string stderr) /// A tuple containing the process exit code, the captured standard output, and the captured standard error. /// If the process times out, the exit code is -1 and the standard error includes a timeout message. /// - public static async Task - RunProcess(string fileName, string arguments, TimeSpan timeout) + public static async Task RunProcess(string fileName, string arguments, TimeSpan timeout) { var psi = new ProcessStartInfo { @@ -82,4 +81,14 @@ public static async Task return new(proc.ExitCode, outputSb.ToString(), errorSb.ToString()); } + + /// + /// Runs a dotnet command asynchronously with the specified arguments and timeout, + /// capturing its exit code, standard output, and standard error. + /// + /// The arguments to pass after dotnet (e.g. tool install Riverside.JsonBinder.Console ...). + /// The maximum duration to wait before forcibly terminating the process. + /// A containing the exit code and captured streams. + public static Task RunNETCoreCliAsync(string arguments, TimeSpan timeout) + => RunProcess("dotnet", arguments, timeout); } From d461701a4428ddf6c25c1be2d277b40a1cbe44df Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:02:55 +0000 Subject: [PATCH 03/20] Fix bad sanitisation --- .../KiotaEngine.Methods.cs | 35 ++++++++++--------- .../Helpers/SanitizationHelpers.cs | 1 - 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Methods.cs b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Methods.cs index cf50361..6031450 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Methods.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Methods.cs @@ -1,4 +1,5 @@ -using System.Text; +using Riverside.CompilerPlatform.Helpers; +using System.Text; namespace Riverside.CompilerPlatform.Features.Swagger; @@ -13,19 +14,19 @@ partial class KiotaEngine /// public override string ToString() { - var command = new StringBuilder().Append("kiota generate"); + var command = new StringBuilder().Append("generate"); if (!string.IsNullOrWhiteSpace(Path)) { - command.Append($" --openapi {Path}"); + command.Append($" --openapi {SanitizationHelpers.EscapeArg(Path!)}"); } if (!string.IsNullOrWhiteSpace(Manifest)) { - command.Append($" --manifest {Manifest}"); + command.Append($" --manifest {SanitizationHelpers.EscapeArg(Manifest!)}"); } if (!string.IsNullOrWhiteSpace(Output)) { - command.Append($" --output {Output}"); + command.Append($" --output {SanitizationHelpers.EscapeArg(Output!)}"); } command.Append($" --language {Language}"); if (!string.IsNullOrWhiteSpace(ClassName)) @@ -56,68 +57,68 @@ public override string ToString() { command.Append($" --additional-data {AdditionalData}"); } - if (Serializer is not null) + if (Serializer is not null && Serializer.Length > 0) { var serializers = new StringBuilder().Append(" --serializer "); foreach (var serializer in Serializer) { serializers.Append(serializer + "|"); } - serializers.Remove(serializers.Length, 1); // remove final '|' char + serializers.Remove(serializers.Length - 1, 1); // remove final '|' char command.Append(serializers.ToString()); } - if (Deserializer is not null) + if (Deserializer is not null && Deserializer.Length > 0) { var deserializers = new StringBuilder().Append(" --deserializer "); foreach (var deserializer in Deserializer) { deserializers.Append(deserializer + "|"); } - deserializers.Remove(deserializers.Length, 1); // remove final '|' char + deserializers.Remove(deserializers.Length - 1, 1); // remove final '|' char command.Append(deserializers.ToString()); } if (CleanOutput is not null) { command.Append($" --clean-output {CleanOutput}"); } - if (StructuredMimeTypes is not null) + if (StructuredMimeTypes is not null && StructuredMimeTypes.Length > 0) { var structuredMimeTypes = new StringBuilder().Append(" --structured-mime-types "); foreach (var structuredMimeType in StructuredMimeTypes) { structuredMimeTypes.Append(structuredMimeType + "|"); } - structuredMimeTypes.Remove(structuredMimeTypes.Length, 1); // remove final '|' char + structuredMimeTypes.Remove(structuredMimeTypes.Length - 1, 1); // remove final '|' char command.Append(structuredMimeTypes.ToString()); } - if (IncludePath is not null) + if (IncludePath is not null && IncludePath.Length > 0) { var includePaths = new StringBuilder().Append(" --include-path "); foreach (var includePath in IncludePath) { includePaths.Append(includePath + "|"); } - includePaths.Remove(includePaths.Length, 1); // remove final '|' char + includePaths.Remove(includePaths.Length - 1, 1); // remove final '|' char command.Append(includePaths.ToString()); } - if (ExcludePath is not null) + if (ExcludePath is not null && ExcludePath.Length > 0) { var excludePaths = new StringBuilder().Append(" --exclude-path "); foreach (var excludePath in ExcludePath) { excludePaths.Append(excludePath + "|"); } - excludePaths.Remove(excludePaths.Length, 1); // remove final '|' char + excludePaths.Remove(excludePaths.Length - 1, 1); // remove final '|' char command.Append(excludePaths.ToString()); } - if (DisableValidationRules is not null) + if (DisableValidationRules is not null && DisableValidationRules.Length > 0) { var disableValidationRules = new StringBuilder().Append(" --disable-validation-rules "); foreach (var disableValidationRule in DisableValidationRules) { disableValidationRules.Append(disableValidationRule + "|"); } - disableValidationRules.Remove(disableValidationRules.Length, 1); // remove final '|' char + disableValidationRules.Remove(disableValidationRules.Length - 1, 1); // remove final '|' char command.Append(disableValidationRules.ToString()); } if (ClearCache is not null) diff --git a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/SanitizationHelpers.cs b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/SanitizationHelpers.cs index 74aea88..70e1823 100644 --- a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/SanitizationHelpers.cs +++ b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/SanitizationHelpers.cs @@ -36,7 +36,6 @@ public static string Sanitize(string s) /// The argument to escape. /// A string containing the escaped argument, suitable for use in a command-line context. /// Thrown if is null. - [NotMyCode] // from the internet public static string EscapeArg(string arg) { if (arg == null) From d571102a57bfb1a4830abd74bb48a2e98d007896 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:03:28 +0000 Subject: [PATCH 04/20] Use `[Diagnostic].Report()` method in `IncrementalGenerator` API --- .../IncrementalGenerator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/roslyn/Riverside.CompilerPlatform.SourceGenerators/IncrementalGenerator.cs b/src/roslyn/Riverside.CompilerPlatform.SourceGenerators/IncrementalGenerator.cs index 0a46157..8feb11b 100644 --- a/src/roslyn/Riverside.CompilerPlatform.SourceGenerators/IncrementalGenerator.cs +++ b/src/roslyn/Riverside.CompilerPlatform.SourceGenerators/IncrementalGenerator.cs @@ -1,4 +1,5 @@ -using System; +using Riverside.CompilerPlatform.Extensions; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -249,12 +250,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (!SuppressDiagnostics) { // Report exceptions that occur during generation - sourceProductionContext.ReportDiagnostic( CreateDiagnostic( $"RS9999", "Source Generation Error", $"An error occurred during source generation: {ex.Message}", - DiagnosticSeverity.Error)); + DiagnosticSeverity.Error).Report(Context); } } }); From b5a6121d9d3eb0dd63cd3aed8543bd29dc18481e Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:04:40 +0000 Subject: [PATCH 05/20] Add extensions to Roslyn primitive analyser config options & dir helper --- .../AnalyzerConfigOptionsExtensions.cs | 92 +++++++++++++++++++ .../Helpers/DirectoryHelpers.cs | 33 +++++++ 2 files changed, 125 insertions(+) create mode 100644 src/roslyn/Riverside.CompilerPlatform.Extensions/Extensions/AnalyzerConfigOptionsExtensions.cs create mode 100644 src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/DirectoryHelpers.cs diff --git a/src/roslyn/Riverside.CompilerPlatform.Extensions/Extensions/AnalyzerConfigOptionsExtensions.cs b/src/roslyn/Riverside.CompilerPlatform.Extensions/Extensions/AnalyzerConfigOptionsExtensions.cs new file mode 100644 index 0000000..858f9d4 --- /dev/null +++ b/src/roslyn/Riverside.CompilerPlatform.Extensions/Extensions/AnalyzerConfigOptionsExtensions.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; + +namespace Riverside.CompilerPlatform.Extensions; + +/// +/// Provides extension methods for reading typed values from . +/// +public static class AnalyzerConfigOptionsExtensions +{ + /// + /// Returns the string value for , or if the key is absent or whitespace. + /// + /// The analyser config options to read from. + /// The property key, typically prefixed with build_property.. + /// The trimmed string value, or . + public static string? GetString(this AnalyzerConfigOptions options, string key) + { + options.TryGetValue(key, out var value); + return string.IsNullOrWhiteSpace(value) ? null : value; + } + + /// + /// Returns a nullable for . + /// Returns when the key is absent, empty, or not a valid boolean string. + /// + /// The analyzer config options to read from. + /// The property key. + /// The parsed boolean, or . + public static bool? GetNullableBool(this AnalyzerConfigOptions options, string key) + { + var value = options.GetString(key); + return value is not null && bool.TryParse(value, out var result) ? result : null; + } + + /// + /// Returns a nullable for , parsed case-insensitively. + /// Returns when the key is absent, empty, or does not map to a valid enum member. + /// + /// The enum type to parse into. + /// The analyser config options to read from. + /// The property key. + /// The parsed enum value, or . + public static TEnum? GetNullableEnum(this AnalyzerConfigOptions options, string key) + where TEnum : struct, Enum + { + var value = options.GetString(key); + return value is not null && Enum.TryParse(value, ignoreCase: true, out var result) ? result : null; + } + + /// + /// Returns a [] by splitting 's value on the | character. + /// Empty or whitespace-only segments are discarded. + /// Returns when the key is absent, empty, or yields no usable segments. + /// + /// The analyser config options to read from. + /// The property key. + /// A non-empty trimmed array of segments, or . + public static string[]? GetPipeSeparatedArray(this AnalyzerConfigOptions options, string key) + { + var value = options.GetString(key); + if (value is null) + return null; + var parts = value.Split('|') + .Select(p => p.Trim()) + .Where(p => p.Length > 0) + .ToArray(); + return parts.Length > 0 ? parts : null; + } + + /// + /// Returns a [] by splitting 's value on | and parsing each segment case-insensitively. + /// Segments that do not match a valid enum member are silently skipped. + /// + /// The enum type to parse each segment into. + /// The analyser config options to read from. + /// The property key. + /// A non-empty array of parsed enum values, or when the is absent, empty, or yields no valid members. + public static TEnum[]? GetPipeSeparatedEnumArray(this AnalyzerConfigOptions options, string key) + where TEnum : struct, Enum + { + var raw = options.GetPipeSeparatedArray(key); + if (raw is null) + return null; + var parsed = raw + .Select(s => (ok: Enum.TryParse(s, ignoreCase: true, out var v), val: v)) + .Where(t => t.ok) + .Select(t => t.val) + .ToArray(); + return parsed.Length > 0 ? parsed : null; + } +} diff --git a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/DirectoryHelpers.cs b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/DirectoryHelpers.cs new file mode 100644 index 0000000..691d6b4 --- /dev/null +++ b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/DirectoryHelpers.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; + +namespace Riverside.CompilerPlatform.Helpers; + +/// +/// Provides utility methods for common directory operations. +/// +public static class DirectoryHelpers +{ + /// + /// Deletes and all of its contents recursively, suppressing any exception that occurs. + /// + /// The directory to delete. + public static void TryDelete(string path) + { + try { if (Directory.Exists(path)) Directory.Delete(path, recursive: true); } + catch { } + } + + /// + /// Creates a uniquely named subdirectory under and returns its full path. + /// The subdirectory name is a compact with no formatting characters. + /// + /// The parent directory. Created if it does not already exist. + /// The full path of the newly created temporary directory. + public static string CreateTemporary(string basePath) + { + var path = Path.Combine(basePath, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(path); + return path; + } +} From 9aafe7e7d26382691e6ca0d2e8d566213f702c21 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:05:02 +0000 Subject: [PATCH 06/20] Fix source generator runtime exception --- .../{api-1.json => Lapse.json} | 0 ...ide.CompilerPlatform.Features.Tests.csproj | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) rename tests/Riverside.CompilerPlatform.Features.Tests/{api-1.json => Lapse.json} (100%) diff --git a/tests/Riverside.CompilerPlatform.Features.Tests/api-1.json b/tests/Riverside.CompilerPlatform.Features.Tests/Lapse.json similarity index 100% rename from tests/Riverside.CompilerPlatform.Features.Tests/api-1.json rename to tests/Riverside.CompilerPlatform.Features.Tests/Lapse.json diff --git a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj index dba8912..54119ac 100644 --- a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj +++ b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj @@ -5,23 +5,24 @@ netstandard2.0 + + + + - - + + + - - - - - - + + - + From 3411656e12c73c16bdf6f3b0ce24b8ea00f5c3c5 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:07:28 +0000 Subject: [PATCH 07/20] Add new Kiota options to Swagger source generator editorconfig options --- .../KiotaGenerator.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs index f4582d6..615d089 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs @@ -1,4 +1,4 @@ -using Riverside.CompilerPlatform.SourceGenerators; +using Riverside.CompilerPlatform.SourceGenerators; using System.Threading; using System; using System.Linq; @@ -16,10 +16,27 @@ namespace Riverside.CompilerPlatform.Features.Swagger; [Generator] public partial class KiotaGenerator : IncrementalGenerator { - private const string VersionProperty = "build_property.KiotaGenerator_Version"; - private const string OptionsProperty = "build_property.KiotaGenerator_Options"; + private const string VersionProperty = "build_property.Kiota_Version"; private const string LanguageProperty = "build_property.KiotaGenerator_Language"; - private const string AdditionalPropertiesProperty = "build_property.KiotaGenerator_AdditionalProperties"; + private const string ClassNameProperty = "build_property.KiotaGenerator_ClassName"; + private const string NamespaceNameProperty = "build_property.KiotaGenerator_NamespaceName"; + private const string TypeAccessModifierProperty = "build_property.KiotaGenerator_TypeAccessModifier"; + private const string LogLevelProperty = "build_property.KiotaGenerator_LogLevel"; + private const string BackingStoreProperty = "build_property.KiotaGenerator_BackingStore"; + private const string ExcludeBackwardCompatibleProperty = "build_property.KiotaGenerator_ExcludeBackwardCompatible"; + private const string AdditionalDataProperty = "build_property.KiotaGenerator_AdditionalData"; + private const string SerializerProperty = "build_property.KiotaGenerator_Serializer"; + private const string DeserializerProperty = "build_property.KiotaGenerator_Deserializer"; + private const string CleanOutputProperty = "build_property.KiotaGenerator_CleanOutput"; + private const string StructuredMimeTypesProperty = "build_property.KiotaGenerator_StructuredMimeTypes"; + private const string IncludePathProperty = "build_property.KiotaGenerator_IncludePath"; + private const string ExcludePathProperty = "build_property.KiotaGenerator_ExcludePath"; + private const string DisableValidationRulesProperty = "build_property.KiotaGenerator_DisableValidationRules"; + private const string ClearCacheProperty = "build_property.KiotaGenerator_ClearCache"; + private const string DisableSSLValidationProperty = "build_property.KiotaGenerator_DisableSSLValidation"; + + private static readonly string ToolDirectory = Path.Combine( + Path.GetTempPath(), "Roslyn", "Advanced Compiler Services for .NET", "KiotaGenerator"); /// protected override void OnBeforeGeneration(GeneratorContext context, CancellationToken cancellationToken) From 9ed0e7dde65ec21c8cd4d9c74e4f35ec577ca153 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:08:25 +0000 Subject: [PATCH 08/20] Introduce new Kiota generator API --- .../KiotaGenerator.cs | 192 ++++++++++-------- 1 file changed, 106 insertions(+), 86 deletions(-) diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs index 615d089..f681249 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs @@ -1,12 +1,12 @@ -using Riverside.CompilerPlatform.SourceGenerators; -using System.Threading; +using Riverside.CompilerPlatform.SourceGenerators; +using Riverside.CompilerPlatform.Extensions; +using Riverside.CompilerPlatform.Helpers; using System; -using System.Linq; using System.Collections.Immutable; using System.IO; +using System.Linq; using System.Text; -using Riverside.CompilerPlatform.Extensions; -using Riverside.CompilerPlatform.Helpers; +using System.Threading; namespace Riverside.CompilerPlatform.Features.Swagger; @@ -41,78 +41,123 @@ public partial class KiotaGenerator : IncrementalGenerator /// protected override void OnBeforeGeneration(GeneratorContext context, CancellationToken cancellationToken) { - var optionsProvider = context.AnalyzerConfigOptions.GlobalOptions; + var options = context.AnalyzerConfigOptions.GlobalOptions; - // OpenAPI specs var specs = context.AdditionalTexts .Where(at => at.Path.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase) || at.Path.EndsWith(".yml", StringComparison.OrdinalIgnoreCase) || at.Path.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) .ToImmutableArray(); - optionsProvider.TryGetValue(VersionProperty, out var version); - optionsProvider.TryGetValue(OptionsProperty, out var cliOptions); - optionsProvider.TryGetValue(LanguageProperty, out var language); - optionsProvider.TryGetValue(AdditionalPropertiesProperty, out var additionalProps); - - version ??= string.Empty; - language ??= "csharp"; + if (specs.IsEmpty) + return; + + var version = options.GetString(VersionProperty); + + // Kiota engine args + var language = options.GetNullableEnum(LanguageProperty) + ?? KiotaEngine.GenerationLanguage.CSharp; + var className = options.GetString(ClassNameProperty); + var namespaceName = options.GetString(NamespaceNameProperty); + var typeAccessModifier = options.GetNullableEnum(TypeAccessModifierProperty); + var logLevel = options.GetNullableEnum(LogLevelProperty); + var backingStore = options.GetNullableBool(BackingStoreProperty); + var excludeBackwardCompatible = options.GetNullableBool(ExcludeBackwardCompatibleProperty); + var additionalData = options.GetNullableBool(AdditionalDataProperty); + var serializers = options.GetPipeSeparatedArray(SerializerProperty); + var deserializers = options.GetPipeSeparatedArray(DeserializerProperty); + var cleanOutput = options.GetNullableBool(CleanOutputProperty); + var structuredMimeTypes = options.GetPipeSeparatedArray(StructuredMimeTypesProperty); + var includePaths = options.GetPipeSeparatedArray(IncludePathProperty); + var excludePaths = options.GetPipeSeparatedArray(ExcludePathProperty); + var disableValidationRules = options.GetPipeSeparatedEnumArray(DisableValidationRulesProperty); + var clearCache = options.GetNullableBool(ClearCacheProperty); + var disableSSLValidation = options.GetNullableBool(DisableSSLValidationProperty); + + string toolExecutable; + try + { + var (installed, installError) = NETCoreToolHelpers + .EnsureToolAsync("Microsoft.OpenApi.Kiota", ToolDirectory, version) + .GetAwaiter().GetResult(); - var jarPath = EnsureToolInstallation(version, context); + if (!installed) + { + CreateDiagnostic( + "KG0000", + "Kiota installation failed", + installError ?? "Failed to install or locate the Kiota tool.").Report(context); + return; + } - if (string.IsNullOrWhiteSpace(jarPath)) + toolExecutable = NETCoreToolHelpers.GetExecutablePath(ToolDirectory, "kiota"); + } + catch (Exception ex) { - IncrementalGenerator.CreateDiagnostic( - "RS0000", - "JAR not downloaded", - "An error occured whilst downloading the JAR executable to generate the OpenAPI spec") - .Report(context); + CreateDiagnostic("KG0000", "Kiota installation failed", ex.Message).Report(context); + return; } foreach (var spec in specs) { - try - { - var specNamespace = SanitizationHelpers.Sanitize(Path.GetFileNameWithoutExtension(Path.GetFileName(spec.Path))); + cancellationToken.ThrowIfCancellationRequested(); - var specPath = spec.Path; - if (!File.Exists(specPath)) - continue; + var specPath = spec.Path; + if (!File.Exists(specPath)) + continue; - var specFileName = Path.GetFileNameWithoutExtension(specPath); + var specFileName = Path.GetFileNameWithoutExtension(specPath); + var effectiveNamespace = namespaceName ?? SanitizationHelpers.Sanitize(specFileName); - var tempOut = Path.Combine(Path.GetTempPath(), "Roslyn", "Advanced Compiler Services for .NET", Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(tempOut); + var tempOut = DirectoryHelpers.CreateTemporary( + Path.Combine(Path.GetTempPath(), "Roslyn", "Advanced Compiler Services for .NET")); - if (!string.IsNullOrWhiteSpace(cliOptions)) - { - argsBuilder.Append(" "); - argsBuilder.Append(cliOptions); - } - - if (!string.IsNullOrWhiteSpace(additionalProps)) - { - argsBuilder.Append(" --additional-properties="); - argsBuilder.Append(SanitizationHelpers.EscapeArg(additionalProps!)); - } - - var args = argsBuilder.ToString(); - - var runResult = ProcessHelpers.RunProcess("java", args, TimeSpan.FromMinutes(2)).GetAwaiter().GetResult(); + try + { + var engine = new KiotaEngine( + d: specPath, + a: null, + o: tempOut, + l: language, + c: className, + n: effectiveNamespace, + tam: typeAccessModifier, + ll: logLevel, + b: backingStore, + ebc: excludeBackwardCompatible, + ad: additionalData, + s: serializers, + ds: deserializers, + co: cleanOutput, + m: structuredMimeTypes, + i: includePaths, + e: excludePaths, + dvr: disableValidationRules, + cc: clearCache, + dsv: disableSSLValidation); + + var runResult = ProcessHelpers + .RunProcess(toolExecutable, engine.ToString(), TimeSpan.FromMinutes(2)) + .GetAwaiter().GetResult(); if (runResult.ExitCode != 0) { - CreateDiagnostic("RS0000", "OpenAPI generator failed", $"OpenAPI generator failed for spec '{spec.Path}' with exit code {runResult.ExitCode}: {runResult.StandardError.ReplaceLineEndings(" ")}").Report(context); - TryDeleteDirectory(tempOut); + CreateDiagnostic( + "KG0001", + "Kiota generation failed", + $"Kiota failed for spec '{specPath}' with exit code {runResult.ExitCode}: {runResult.StandardError.ReplaceLineEndings(" ")}").Report(context); + DirectoryHelpers.TryDelete(tempOut); continue; } - var srcDir = Path.Combine(tempOut, "src", specNamespace); - var csFiles = Directory.EnumerateFiles(srcDir, "*.cs", SearchOption.AllDirectories).ToArray(); + var csFiles = Directory.EnumerateFiles(tempOut, "*.cs", SearchOption.AllDirectories).ToArray(); if (csFiles.Length == 0) { - CreateDiagnostic("RS0000", "No C# files generated", $"OpenAPI generator produced no C# files for spec '{spec.Path}'").Report(context); - TryDeleteDirectory(tempOut); + CreateDiagnostic( + "KG0002", + "No C# files generated", + $"Kiota produced no C# files for spec '{specPath}'").Report(context); + DirectoryHelpers.TryDelete(tempOut); continue; } @@ -122,52 +167,27 @@ protected override void OnBeforeGeneration(GeneratorContext context, Cancellatio { var content = File.ReadAllText(cs, Encoding.UTF8); var rel = Path.GetRelativePath(tempOut, cs) - .Replace(Path.DirectorySeparatorChar, '_') - .Replace(Path.AltDirectorySeparatorChar, '_'); - - var hintName = $"{SanitizationHelpers.Sanitize(specFileName)}_{SanitizationHelpers.Sanitize(rel)}"; + .Replace(Path.DirectorySeparatorChar, '.') + .Replace(Path.AltDirectorySeparatorChar, '.'); + var hintName = $"{SanitizationHelpers.Sanitize(engine.NamespaceName!)}.{SanitizationHelpers.Sanitize(rel)}"; AddSource(hintName, content); } catch (Exception ex) { - CreateDiagnostic("RS0000", "Failed to add generated file", $"Failed to add generated file '{cs}': {ex.Message}").Report(context); + CreateDiagnostic( + "KG0003", + "Failed to add generated file", + $"Failed to add '{cs}': {ex.Message}").Report(context); } } - TryDeleteDirectory(tempOut); - - AddSource($"{SanitizationHelpers.Sanitize(specFileName)}_AnyOf", AnyOf_Polyfill(specNamespace + ".Model")); + //DirectoryHelpers.TryDelete(tempOut); } catch (Exception ex) { - CreateDiagnostic("RS9999", "OpenAPI generator exception", ex.ToString()); + CreateDiagnostic("KG9999", "Kiota generator exception", ex.ToString()).Report(context); + DirectoryHelpers.TryDelete(tempOut); } } } - - private static string? EnsureToolInstallation(string version, GeneratorContext context) - { - try - { - var baseDir = Path.Combine(Path.GetTempPath(), "Roslyn", "Advanced Compiler Services for .NET", "KiotaGenerator"); - Directory.CreateDirectory(baseDir); - - var jarPath = Path.Combine(baseDir, $"openapi-generator-cli-{version}.jar"); - if (File.Exists(jarPath)) - return jarPath; - - return jarPath; - } - catch (Exception ex) - { - CreateDiagnostic("RS0000", $"Failed to download OpenAPI generator JAR", $"Could not download version {version}: {ex.Message}").Report(context); - return null; - } - } - - private static void TryDeleteDirectory(string path) - { - try { if (Directory.Exists(path)) Directory.Delete(path, true); } - catch { } - } } From 9fe5d31492b3f50c4dd8f58214bbf8110262593c Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:09:49 +0000 Subject: [PATCH 09/20] Remove testing values & correctly reference "Microsoft Kiota" --- .../KiotaGenerator.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs index f681249..d6e2f80 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs @@ -85,8 +85,8 @@ protected override void OnBeforeGeneration(GeneratorContext context, Cancellatio { CreateDiagnostic( "KG0000", - "Kiota installation failed", - installError ?? "Failed to install or locate the Kiota tool.").Report(context); + "Microsoft Kiota installation failed", + installError ?? "Failed to install or locate the Microsoft Kiota tool.").Report(context); return; } @@ -94,7 +94,7 @@ protected override void OnBeforeGeneration(GeneratorContext context, Cancellatio } catch (Exception ex) { - CreateDiagnostic("KG0000", "Kiota installation failed", ex.Message).Report(context); + CreateDiagnostic("KG0000", "Microsoft Kiota installation failed", ex.Message).Report(context); return; } @@ -144,8 +144,8 @@ protected override void OnBeforeGeneration(GeneratorContext context, Cancellatio { CreateDiagnostic( "KG0001", - "Kiota generation failed", - $"Kiota failed for spec '{specPath}' with exit code {runResult.ExitCode}: {runResult.StandardError.ReplaceLineEndings(" ")}").Report(context); + "OpenAPI generation failed", + $"Microsoft Kiota failed for spec '{specPath}' with exit code {runResult.ExitCode}: {runResult.StandardError.ReplaceLineEndings(" ")}").Report(context); DirectoryHelpers.TryDelete(tempOut); continue; } @@ -156,7 +156,7 @@ protected override void OnBeforeGeneration(GeneratorContext context, Cancellatio CreateDiagnostic( "KG0002", "No C# files generated", - $"Kiota produced no C# files for spec '{specPath}'").Report(context); + $"Microsoft Kiota produced no C# files for spec '{specPath}'").Report(context); DirectoryHelpers.TryDelete(tempOut); continue; } @@ -181,11 +181,11 @@ protected override void OnBeforeGeneration(GeneratorContext context, Cancellatio } } - //DirectoryHelpers.TryDelete(tempOut); + DirectoryHelpers.TryDelete(tempOut); } catch (Exception ex) { - CreateDiagnostic("KG9999", "Kiota generator exception", ex.ToString()).Report(context); + CreateDiagnostic("KG9999", "OpenAPI generator exception", ex.ToString()).Report(context); DirectoryHelpers.TryDelete(tempOut); } } From ffd8050adf6a9504a5f36ab4b9467d72abb963f8 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:14:03 +0000 Subject: [PATCH 10/20] Completely rename "Swagger" project to "Kiota" --- .github/workflows/ci.yml | 4 ++-- CompilerPlatform.slnx | 2 +- .../KiotaEngine.Enums.cs | 2 +- .../KiotaEngine.Methods.cs | 2 +- .../KiotaEngine.cs | 2 +- .../KiotaGenerator.cs | 2 +- .../Riverside.CompilerPlatform.Features.Kiota.csproj} | 0 .../Riverside.CompilerPlatform.Features.Tests.csproj | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) rename src/features/{Riverside.CompilerPlatform.Features.Swagger => Riverside.CompilerPlatform.Features.Kiota}/KiotaEngine.Enums.cs (91%) rename src/features/{Riverside.CompilerPlatform.Features.Swagger => Riverside.CompilerPlatform.Features.Kiota}/KiotaEngine.Methods.cs (98%) rename src/features/{Riverside.CompilerPlatform.Features.Swagger => Riverside.CompilerPlatform.Features.Kiota}/KiotaEngine.cs (99%) rename src/features/{Riverside.CompilerPlatform.Features.Swagger => Riverside.CompilerPlatform.Features.Kiota}/KiotaGenerator.cs (99%) rename src/features/{Riverside.CompilerPlatform.Features.Swagger/Riverside.CompilerPlatform.Features.Swagger.csproj => Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a51ba6..d2aed10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,12 +16,12 @@ jobs: fail-fast: false matrix: configuration: [CSharp, VisualBasic] - project: [Analyzers, CodeFixers, Extensions, HighPerformance, SourceGenerators, DynamicCast, Swagger] + project: [Analyzers, CodeFixers, Extensions, HighPerformance, SourceGenerators, DynamicCast, Kiota] exclude: - configuration: VisualBasic project: DynamicCast - configuration: VisualBasic - project: Swagger + project: Kiota env: PROJECT: ${{ matrix.project }} diff --git a/CompilerPlatform.slnx b/CompilerPlatform.slnx index 75a6797..cc4cc7d 100644 --- a/CompilerPlatform.slnx +++ b/CompilerPlatform.slnx @@ -9,7 +9,7 @@ - + diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Enums.cs b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs similarity index 91% rename from src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Enums.cs rename to src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs index 46ee5ea..90663f7 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Enums.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs @@ -1,4 +1,4 @@ -namespace Riverside.CompilerPlatform.Features.Swagger; +namespace Riverside.CompilerPlatform.Features.Kiota; partial class KiotaEngine { diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Methods.cs b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Methods.cs similarity index 98% rename from src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Methods.cs rename to src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Methods.cs index 6031450..ac5ead9 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.Methods.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Methods.cs @@ -1,7 +1,7 @@ using Riverside.CompilerPlatform.Helpers; using System.Text; -namespace Riverside.CompilerPlatform.Features.Swagger; +namespace Riverside.CompilerPlatform.Features.Kiota; partial class KiotaEngine { diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.cs b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.cs similarity index 99% rename from src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.cs rename to src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.cs index d513be6..aff3c43 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaEngine.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace Riverside.CompilerPlatform.Features.Swagger; +namespace Riverside.CompilerPlatform.Features.Kiota; /// /// Represents the configuration and options for generating code using the Kiota engine. diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs similarity index 99% rename from src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs rename to src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs index d6e2f80..f9b5924 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Swagger/KiotaGenerator.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs @@ -8,7 +8,7 @@ using System.Text; using System.Threading; -namespace Riverside.CompilerPlatform.Features.Swagger; +namespace Riverside.CompilerPlatform.Features.Kiota; /// /// Generates source code from OpenAPI specification files as part of the build process. diff --git a/src/features/Riverside.CompilerPlatform.Features.Swagger/Riverside.CompilerPlatform.Features.Swagger.csproj b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj similarity index 100% rename from src/features/Riverside.CompilerPlatform.Features.Swagger/Riverside.CompilerPlatform.Features.Swagger.csproj rename to src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj diff --git a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj index 54119ac..8a39d30 100644 --- a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj +++ b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj @@ -11,7 +11,7 @@ - + From cf7c8abf56c7721c26f4d9f198e6fa25a1ff1086 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:15:07 +0000 Subject: [PATCH 11/20] Remove unnecessary IDs from solution file --- CompilerPlatform.slnx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CompilerPlatform.slnx b/CompilerPlatform.slnx index cc4cc7d..de535ba 100644 --- a/CompilerPlatform.slnx +++ b/CompilerPlatform.slnx @@ -9,7 +9,7 @@ - + @@ -26,7 +26,7 @@ - + From 97f8b63284e007533a8cc2a32a0fd9f01729e299 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:25:58 +0000 Subject: [PATCH 12/20] Remove accidental spaces indentation usage I am a bad person!! --- .../Helpers/DirectoryHelpers.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/DirectoryHelpers.cs b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/DirectoryHelpers.cs index 691d6b4..99a0167 100644 --- a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/DirectoryHelpers.cs +++ b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/DirectoryHelpers.cs @@ -8,26 +8,26 @@ namespace Riverside.CompilerPlatform.Helpers; /// public static class DirectoryHelpers { - /// - /// Deletes and all of its contents recursively, suppressing any exception that occurs. - /// - /// The directory to delete. - public static void TryDelete(string path) - { - try { if (Directory.Exists(path)) Directory.Delete(path, recursive: true); } - catch { } - } + /// + /// Deletes and all of its contents recursively, suppressing any exception that occurs. + /// + /// The directory to delete. + public static void TryDelete(string path) + { + try { if (Directory.Exists(path)) Directory.Delete(path, recursive: true); } + catch { } + } - /// - /// Creates a uniquely named subdirectory under and returns its full path. - /// The subdirectory name is a compact with no formatting characters. - /// - /// The parent directory. Created if it does not already exist. - /// The full path of the newly created temporary directory. - public static string CreateTemporary(string basePath) - { - var path = Path.Combine(basePath, Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(path); - return path; - } + /// + /// Creates a uniquely named subdirectory under and returns its full path. + /// The subdirectory name is a compact with no formatting characters. + /// + /// The parent directory. Created if it does not already exist. + /// The full path of the newly created temporary directory. + public static string CreateTemporary(string basePath) + { + var path = Path.Combine(basePath, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(path); + return path; + } } From 4e0f8e179912c4600dd8a9b854c7e3f27f269013 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:37:17 +0000 Subject: [PATCH 13/20] Add `Kiota.props` --- .../Kiota.props | 67 +++++++++++++++++++ ...ide.CompilerPlatform.Features.Kiota.csproj | 5 ++ 2 files changed, 72 insertions(+) create mode 100644 src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props b/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props new file mode 100644 index 0000000..75bb4e3 --- /dev/null +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props @@ -0,0 +1,67 @@ + + + + 7.3 + 1.21.2 + 1.21.2 + + + + + + + + + + + + <_Kiota_TargetFramework Include="$(TargetFramework)" Condition="'$(TargetFramework)' != ''" /> + <_Kiota_TargetFramework Include="$(TargetFrameworks)" Condition="'$(TargetFrameworks)' != ''" /> + <_Kiota_InvalidFramework Include="@(_Kiota_TargetFramework)" + Condition="!$([System.Text.RegularExpressions.Regex]::IsMatch('%(Identity)', '^netstandard2\.(0|1)$|^net462$|^net([8-9]|[1-9][0-9])(?:\.[0-9]+)?(?:[-.].*)?$'))" /> + + + + + + + + + + + + + + diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj index f8ca381..dd149f1 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj @@ -10,6 +10,11 @@ + + + + + all From 8f765948cbac175ca0711446a7814aa30dd295ae Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:52:06 +0000 Subject: [PATCH 14/20] Clean up --- Directory.Build.props | 3 +++ .../KiotaGenerator.cs | 4 ++-- .../Helpers/SanitizationHelpers.cs | 3 +-- .../IncrementalGenerator.cs | 10 +++++----- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index a540c34..1f9add8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,8 @@ + + $(MSBuildThisFileDirectory) $(RootDirectory)src @@ -30,4 +32,5 @@ + \ No newline at end of file diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs index f9b5924..09c21a0 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs @@ -1,6 +1,6 @@ -using Riverside.CompilerPlatform.SourceGenerators; -using Riverside.CompilerPlatform.Extensions; +using Riverside.CompilerPlatform.Extensions; using Riverside.CompilerPlatform.Helpers; +using Riverside.CompilerPlatform.SourceGenerators; using System; using System.Collections.Immutable; using System.IO; diff --git a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/SanitizationHelpers.cs b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/SanitizationHelpers.cs index 70e1823..a258a42 100644 --- a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/SanitizationHelpers.cs +++ b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/SanitizationHelpers.cs @@ -1,5 +1,4 @@ -using Riverside.Extensions.Accountability; -using System; +using System; using System.IO; using System.Linq; using System.Text; diff --git a/src/roslyn/Riverside.CompilerPlatform.SourceGenerators/IncrementalGenerator.cs b/src/roslyn/Riverside.CompilerPlatform.SourceGenerators/IncrementalGenerator.cs index 8feb11b..8d9f615 100644 --- a/src/roslyn/Riverside.CompilerPlatform.SourceGenerators/IncrementalGenerator.cs +++ b/src/roslyn/Riverside.CompilerPlatform.SourceGenerators/IncrementalGenerator.cs @@ -250,11 +250,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context) if (!SuppressDiagnostics) { // Report exceptions that occur during generation - CreateDiagnostic( - $"RS9999", - "Source Generation Error", - $"An error occurred during source generation: {ex.Message}", - DiagnosticSeverity.Error).Report(Context); + CreateDiagnostic( + $"RS9999", + "Source Generation Error", + $"An error occurred during source generation: {ex.Message}", + DiagnosticSeverity.Error).Report(Context); } } }); From 505dc8478e2d83a6ce6c80d34f659734e51dfac9 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:35:34 +0000 Subject: [PATCH 15/20] Initial progress on Kiota generator publishing --- .../Kiota.props | 4 ++++ ...ide.CompilerPlatform.Features.Kiota.csproj | 20 +++++++++++++++++-- .../{Lapse.json => Lapse12345.json} | 0 ...ide.CompilerPlatform.Features.Tests.csproj | 8 +++++--- 4 files changed, 27 insertions(+), 5 deletions(-) rename tests/Riverside.CompilerPlatform.Features.Tests/{Lapse.json => Lapse12345.json} (100%) diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props b/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props index 75bb4e3..ba4080b 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props @@ -20,6 +20,10 @@ + + + + <_Kiota_TargetFramework Include="$(TargetFramework)" Condition="'$(TargetFramework)' != ''" /> <_Kiota_TargetFramework Include="$(TargetFrameworks)" Condition="'$(TargetFrameworks)' != ''" /> diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj index dd149f1..cdd8711 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj @@ -3,11 +3,14 @@ netstandard2.0 CSharp + true + false + $(NoWarn);NU5128 - - + + @@ -22,4 +25,17 @@ + + + + + + + + diff --git a/tests/Riverside.CompilerPlatform.Features.Tests/Lapse.json b/tests/Riverside.CompilerPlatform.Features.Tests/Lapse12345.json similarity index 100% rename from tests/Riverside.CompilerPlatform.Features.Tests/Lapse.json rename to tests/Riverside.CompilerPlatform.Features.Tests/Lapse12345.json diff --git a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj index 8a39d30..4d472a1 100644 --- a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj +++ b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj @@ -3,6 +3,7 @@ VisualBasic;CSharp netstandard2.0 + 14.0 @@ -10,19 +11,20 @@ - + + - + From 03be3b40b1b6df2cd6cc9800ffb262fb0b9ec524 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 21:51:38 +0000 Subject: [PATCH 16/20] Remove unnecessary code --- .../Riverside.CompilerPlatform.Features.Kiota/Kiota.props | 4 ---- .../Riverside.CompilerPlatform.Features.Kiota.csproj | 1 - 2 files changed, 5 deletions(-) diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props b/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props index ba4080b..75bb4e3 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props @@ -20,10 +20,6 @@ - - - - <_Kiota_TargetFramework Include="$(TargetFramework)" Condition="'$(TargetFramework)' != ''" /> <_Kiota_TargetFramework Include="$(TargetFrameworks)" Condition="'$(TargetFrameworks)' != ''" /> diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj index cdd8711..582a3a2 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj @@ -3,7 +3,6 @@ netstandard2.0 CSharp - true false $(NoWarn);NU5128 From ff7d05c8e5ad0f9777705f41acd2d11d05594e51 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:11:26 +0000 Subject: [PATCH 17/20] Fix ambiguity between package ID and command name in `NETCoreToolHelper` --- .../KiotaGenerator.cs | 2 +- .../Helpers/NETCoreToolHelpers.cs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs index 09c21a0..177f3a9 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaGenerator.cs @@ -78,7 +78,7 @@ protected override void OnBeforeGeneration(GeneratorContext context, Cancellatio try { var (installed, installError) = NETCoreToolHelpers - .EnsureToolAsync("Microsoft.OpenApi.Kiota", ToolDirectory, version) + .EnsureToolAsync("Microsoft.OpenApi.Kiota", ToolDirectory, version, commandName: "kiota") .GetAwaiter().GetResult(); if (!installed) diff --git a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/NETCoreToolHelpers.cs b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/NETCoreToolHelpers.cs index ea1c479..a80df77 100644 --- a/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/NETCoreToolHelpers.cs +++ b/src/roslyn/Riverside.CompilerPlatform.Extensions/Helpers/NETCoreToolHelpers.cs @@ -31,22 +31,28 @@ public static string GetExecutablePath(string toolDirectory, string toolName) /// Installation succeeds when the executable is present after the above steps. /// /// - /// The NuGet package ID of the tool (e.g. Riverside.JsonBinder.Console). + /// The NuGet package ID of the tool (e.g. Riverside.JsonBinder.Console). /// The directory to install the tool into, passed to --tool-path. /// /// A specific version to pin. Pass to install or keep the latest. /// /// Maximum wait time per install or update process. Defaults to 5 minutes. + /// + /// The executable/command name to look for in (e.g. jsonbinder). + /// If , is used as the command name. + /// /// /// A tuple where Success is when the executable is available, and Error carries the captured stderr when installation fails. /// public static async Task<(bool Success, string? Error)> EnsureToolAsync( - string toolName, + string packageId, string toolDirectory, string? version = null, - TimeSpan? timeout = null) + TimeSpan? timeout = null, + string? commandName = null) { - var exe = GetExecutablePath(toolDirectory, toolName); + var exeName = string.IsNullOrWhiteSpace(commandName) ? packageId : commandName; + var exe = GetExecutablePath(toolDirectory, exeName!); var effectiveTimeout = timeout ?? TimeSpan.FromMinutes(5); Directory.CreateDirectory(toolDirectory); @@ -58,14 +64,14 @@ public static string GetExecutablePath(string toolDirectory, string toolName) var versionArg = string.IsNullOrWhiteSpace(version) ? string.Empty : $" --version {version}"; var installResult = await ProcessHelpers.RunNETCoreCliAsync( - $"tool install {toolName} {toolPathArg}{versionArg}", effectiveTimeout); + $"tool install {packageId} {toolPathArg}{versionArg}", effectiveTimeout); if (installResult.ExitCode == 0) return (true, null); // install exits non-zero when the tool is already present; attempt an update instead var updateResult = await ProcessHelpers.RunNETCoreCliAsync( - $"tool update {toolName} {toolPathArg}{versionArg}", effectiveTimeout); + $"tool update {packageId} {toolPathArg}{versionArg}", effectiveTimeout); return File.Exists(exe) ? (true, null) From d06fca04b9699e968dd0cd89d2474b440078f059 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:13:07 +0000 Subject: [PATCH 18/20] Reference only Kiota project in tests --- .../Riverside.CompilerPlatform.Features.Tests.csproj | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj index 4d472a1..b673344 100644 --- a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj +++ b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj @@ -1,4 +1,4 @@ - + VisualBasic;CSharp @@ -18,9 +18,7 @@ - - - + From 664068d45588c357df37f7c71b783334c5bc8810 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:17:48 +0000 Subject: [PATCH 19/20] Remove implicit reference to `Microsoft.Kiota` packages --- .../Riverside.CompilerPlatform.Features.Kiota/Kiota.props | 4 ++-- .../Riverside.CompilerPlatform.Features.Kiota.csproj | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props b/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props index 75bb4e3..aa54407 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/Kiota.props @@ -6,7 +6,7 @@ 1.21.2 - + diff --git a/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj index 582a3a2..b3b6e17 100644 --- a/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj +++ b/src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj @@ -22,6 +22,8 @@ all runtime; build; native; contentfiles; analyzers + + From 9b72aafd4543991f269df4e55c84c8186393b962 Mon Sep 17 00:00:00 2001 From: Lamparter <71598437+Lamparter@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:20:55 +0000 Subject: [PATCH 20/20] Disassemble Kiota testing framework --- .../{Lapse12345.json => Lapse.json} | 0 .../Riverside.CompilerPlatform.Features.Tests.csproj | 12 +++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) rename tests/Riverside.CompilerPlatform.Features.Tests/{Lapse12345.json => Lapse.json} (100%) diff --git a/tests/Riverside.CompilerPlatform.Features.Tests/Lapse12345.json b/tests/Riverside.CompilerPlatform.Features.Tests/Lapse.json similarity index 100% rename from tests/Riverside.CompilerPlatform.Features.Tests/Lapse12345.json rename to tests/Riverside.CompilerPlatform.Features.Tests/Lapse.json diff --git a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj index b673344..b186e42 100644 --- a/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj +++ b/tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj @@ -1,4 +1,4 @@ - + VisualBasic;CSharp @@ -11,18 +11,20 @@ - + - + + + - +