< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Languages.CSharpDelegateBuilder
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Languages/CSharpDelegateBuilder.cs
Tag: Kestrun/Kestrun@ca54e35c77799b76774b3805b6f075cdbc0c5fbe
Line coverage
82%
Covered lines: 203
Uncovered lines: 44
Coverable lines: 247
Total lines: 688
Line coverage: 82.1%
Branch coverage
66%
Covered branches: 121
Total branches: 182
Branch coverage: 66.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 09/08/2025 - 20:34:03 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/09/2025 - 05:44:24 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@a26a91936c400a7f2324671b2222643fb772438109/09/2025 - 20:36:29 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@eec6e531f6ea893bb4939db76943e009d9fd963c09/09/2025 - 21:56:59 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@739093f321f10605cc4d1029da7300e3bb4dcba909/10/2025 - 17:50:34 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@5f41b7385e6492ec5892c0c7887658952f6fb87e09/10/2025 - 19:58:59 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb09/12/2025 - 13:06:50 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@0cd8aff9570206f0b23cbe71d96e965f66ee14fa09/12/2025 - 13:32:05 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@63ea5841fe73fd164406accba17a956e8c08357f09/12/2025 - 13:43:03 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@bd4d239cd63303706ae393e27d54f868ef64f9ff09/12/2025 - 16:20:13 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@bd014be0a15f3c9298922d2ff67068869adda2a009/12/2025 - 17:01:20 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@383c1560c6be4027d79f183f7ed839edb887efea09/12/2025 - 17:39:52 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@7189783cf8fc454ea15d22b08b77bebbd01f968709/12/2025 - 21:57:57 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@7f3035804f966a691bd6936a199a8086730a784509/13/2025 - 17:19:56 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@ea635f1ee1937c260a89d1a43a3c203cd8767c7b09/13/2025 - 21:11:10 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@c00c04d65edffc6840698a5c67a70cae1ad411d909/14/2025 - 21:23:16 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@c9d2f0b3dd164d7dc0dc2407a9f006293d92422309/15/2025 - 19:16:35 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@bfb58693b9baaed61644ace5b29e014d9ffacbc909/16/2025 - 04:01:29 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@e5263347b0baba68d9fd62ffbf60a7dd87f994bb09/16/2025 - 16:28:42 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@d5c0d6132e97ca542441289c02a4c9e9d0364d4909/16/2025 - 18:38:10 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@1ec65c49ba24bea275273220d19054072659b62a09/16/2025 - 19:34:38 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@cd031acff6ee2a77514aa4ff9c66847f9e475e6010/13/2025 - 16:52:37 Line coverage: 83.1% (202/243) Branch coverage: 68.3% (123/180) Total lines: 665 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/15/2025 - 01:01:18 Line coverage: 83.1% (202/243) Branch coverage: 68.8% (124/180) Total lines: 665 Tag: Kestrun/Kestrun@7c4ce528870211ad6c2d2398c31ec13097fc584010/16/2025 - 12:37:55 Line coverage: 83.1% (202/243) Branch coverage: 68.3% (123/180) Total lines: 665 Tag: Kestrun/Kestrun@fe9b0d5b0046c725ac092fdc6c0e022ea3ddbd0610/17/2025 - 15:48:30 Line coverage: 83.1% (202/243) Branch coverage: 68.8% (124/180) Total lines: 665 Tag: Kestrun/Kestrun@b8199aff869a847b75e185d0527ba45e04a43d8610/17/2025 - 17:13:00 Line coverage: 83.1% (202/243) Branch coverage: 68.3% (123/180) Total lines: 665 Tag: Kestrun/Kestrun@72ffc26b6676ccf0f60d50c94e4bb52de23743be10/27/2025 - 15:14:42 Line coverage: 83.1% (202/243) Branch coverage: 68.8% (124/180) Total lines: 665 Tag: Kestrun/Kestrun@29b7a3fdf10e67fea44e784a49929d1eb2a8874611/14/2025 - 12:29:34 Line coverage: 82% (201/245) Branch coverage: 67% (122/182) Total lines: 677 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a62612/12/2025 - 17:27:19 Line coverage: 82% (201/245) Branch coverage: 66.4% (121/182) Total lines: 677 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd01/21/2026 - 17:07:46 Line coverage: 82.1% (203/247) Branch coverage: 66.4% (121/182) Total lines: 688 Tag: Kestrun/Kestrun@3f6f61710c7ef7d5953cab578fe699c1e5e01a36 09/08/2025 - 20:34:03 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/09/2025 - 05:44:24 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@a26a91936c400a7f2324671b2222643fb772438109/09/2025 - 20:36:29 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@eec6e531f6ea893bb4939db76943e009d9fd963c09/09/2025 - 21:56:59 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@739093f321f10605cc4d1029da7300e3bb4dcba909/10/2025 - 17:50:34 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@5f41b7385e6492ec5892c0c7887658952f6fb87e09/10/2025 - 19:58:59 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@9d3a582b2d63930269564a7591aa77ef297cadeb09/12/2025 - 13:06:50 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@0cd8aff9570206f0b23cbe71d96e965f66ee14fa09/12/2025 - 13:32:05 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@63ea5841fe73fd164406accba17a956e8c08357f09/12/2025 - 13:43:03 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@bd4d239cd63303706ae393e27d54f868ef64f9ff09/12/2025 - 16:20:13 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@bd014be0a15f3c9298922d2ff67068869adda2a009/12/2025 - 17:01:20 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@383c1560c6be4027d79f183f7ed839edb887efea09/12/2025 - 17:39:52 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@7189783cf8fc454ea15d22b08b77bebbd01f968709/12/2025 - 21:57:57 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@7f3035804f966a691bd6936a199a8086730a784509/13/2025 - 17:19:56 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@ea635f1ee1937c260a89d1a43a3c203cd8767c7b09/13/2025 - 21:11:10 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@c00c04d65edffc6840698a5c67a70cae1ad411d909/14/2025 - 21:23:16 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@c9d2f0b3dd164d7dc0dc2407a9f006293d92422309/15/2025 - 19:16:35 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@bfb58693b9baaed61644ace5b29e014d9ffacbc909/16/2025 - 04:01:29 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@e5263347b0baba68d9fd62ffbf60a7dd87f994bb09/16/2025 - 16:28:42 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@d5c0d6132e97ca542441289c02a4c9e9d0364d4909/16/2025 - 18:38:10 Line coverage: 82.2% (190/231) Branch coverage: 69.2% (126/182) Total lines: 586 Tag: Kestrun/Kestrun@1ec65c49ba24bea275273220d19054072659b62a09/16/2025 - 19:34:38 Line coverage: 82.2% (190/231) Branch coverage: 68.6% (125/182) Total lines: 586 Tag: Kestrun/Kestrun@cd031acff6ee2a77514aa4ff9c66847f9e475e6010/13/2025 - 16:52:37 Line coverage: 83.1% (202/243) Branch coverage: 68.3% (123/180) Total lines: 665 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/15/2025 - 01:01:18 Line coverage: 83.1% (202/243) Branch coverage: 68.8% (124/180) Total lines: 665 Tag: Kestrun/Kestrun@7c4ce528870211ad6c2d2398c31ec13097fc584010/16/2025 - 12:37:55 Line coverage: 83.1% (202/243) Branch coverage: 68.3% (123/180) Total lines: 665 Tag: Kestrun/Kestrun@fe9b0d5b0046c725ac092fdc6c0e022ea3ddbd0610/17/2025 - 15:48:30 Line coverage: 83.1% (202/243) Branch coverage: 68.8% (124/180) Total lines: 665 Tag: Kestrun/Kestrun@b8199aff869a847b75e185d0527ba45e04a43d8610/17/2025 - 17:13:00 Line coverage: 83.1% (202/243) Branch coverage: 68.3% (123/180) Total lines: 665 Tag: Kestrun/Kestrun@72ffc26b6676ccf0f60d50c94e4bb52de23743be10/27/2025 - 15:14:42 Line coverage: 83.1% (202/243) Branch coverage: 68.8% (124/180) Total lines: 665 Tag: Kestrun/Kestrun@29b7a3fdf10e67fea44e784a49929d1eb2a8874611/14/2025 - 12:29:34 Line coverage: 82% (201/245) Branch coverage: 67% (122/182) Total lines: 677 Tag: Kestrun/Kestrun@5e12b09a6838e68e704cd3dc975331b9e680a62612/12/2025 - 17:27:19 Line coverage: 82% (201/245) Branch coverage: 66.4% (121/182) Total lines: 677 Tag: Kestrun/Kestrun@826bf9dcf9db118c5de4c78a3259bce9549f0dcd01/21/2026 - 17:07:46 Line coverage: 82.1% (203/247) Branch coverage: 66.4% (121/182) Total lines: 688 Tag: Kestrun/Kestrun@3f6f61710c7ef7d5953cab578fe699c1e5e01a36

Metrics

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Languages/CSharpDelegateBuilder.cs

#LineLine coverage
 1using System.Collections.Immutable;
 2using System.Reflection;
 3using System.Text;
 4using Kestrun.SharedState;
 5using Microsoft.CodeAnalysis;
 6using Microsoft.CodeAnalysis.CSharp;
 7using Microsoft.CodeAnalysis.CSharp.Scripting;
 8using Microsoft.CodeAnalysis.Scripting;
 9using Serilog.Events;
 10using Kestrun.Logging;
 11using Kestrun.Hosting;
 12
 13namespace Kestrun.Languages;
 14
 15internal static class CSharpDelegateBuilder
 16{
 17    /// <summary>
 18    /// Builds a C# delegate for handling HTTP requests.
 19    /// </summary>
 20    /// <param name="host">The Kestrun host instance.</param>
 21    /// <param name="code">The C# code to execute.</param>
 22    /// <param name="args">Arguments to inject as variables into the script.</param>
 23    /// <param name="extraImports">Additional namespaces to import.</param>
 24    /// <param name="extraRefs">Additional assemblies to reference.</param>
 25    /// <param name="languageVersion">The C# language version to use.</param>
 26    /// <returns>A delegate that handles HTTP requests.</returns>
 27    /// <exception cref="ArgumentNullException">Thrown if the code is null or whitespace.</exception>
 28    /// <exception cref="CompilationErrorException">Thrown if the C# code compilation fails.</exception>
 29    /// <remarks>
 30    /// This method compiles the provided C# code into a script and returns a delegate that can be used to handle HTTP r
 31    /// It supports additional imports and references, and can inject global variables into the script.
 32    /// The delegate will execute the provided C# code within the context of an HTTP request, allowing access to the req
 33    /// </remarks>
 34    internal static RequestDelegate Build(KestrunHost host,
 35            string code, Dictionary<string, object?>? args, string[]? extraImports,
 36            Assembly[]? extraRefs, LanguageVersion languageVersion = LanguageVersion.CSharp12)
 37    {
 3738        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 39        {
 2640            host.Logger.Debug("Building C# delegate, script length={Length}, imports={ImportsCount}, refs={RefsCount}, l
 2641                code?.Length, extraImports?.Length ?? 0, extraRefs?.Length ?? 0, languageVersion);
 42        }
 43
 44        // Validate inputs
 3745        if (string.IsNullOrWhiteSpace(code))
 46        {
 147            throw new ArgumentNullException(nameof(code), "C# code cannot be null or whitespace.");
 48        }
 49        // 1. Compile the C# code into a script
 50        //    - Use CSharpScript.Create() to create a script with the provided code
 51        //    - Use ScriptOptions to specify imports, references, and language version
 52        //    - Inject the provided arguments into the globals
 3653        var script = Compile(host: host, code: code, extraImports: extraImports, extraRefs: extraRefs, null, languageVer
 54
 55        // 2. Return a delegate that executes the script
 56        //    - The delegate takes an HttpContext and returns a Task
 57        //    - It creates a KestrunContext and KestrunResponse from the HttpContext
 58        //    - It executes the script with the provided globals and locals
 59        //    - It applies the response to the HttpContext
 3660        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 61        {
 2562            host.Logger.Debug("C# delegate built successfully, script length={Length}, imports={ImportsCount}, refs={Ref
 2563                code?.Length, extraImports?.Length ?? 0, extraRefs?.Length ?? 0, languageVersion);
 64        }
 3665        return BuildExecutionDelegate(host, script, args);
 66    }
 67
 68    /// <summary>
 69    /// Builds the execution delegate for the compiled C# script.
 70    /// </summary>
 71    /// <param name="host">The Kestrun host instance.</param>
 72    /// <param name="script">The compiled C# script.</param>
 73    /// <param name="args">Arguments to inject as variables into the script.</param>
 74    /// <returns>A delegate that handles HTTP requests.</returns>
 75    private static RequestDelegate BuildExecutionDelegate(KestrunHost host, Script<object> script, Dictionary<string, ob
 76    {
 3677        return async ctx =>
 3678        {
 1579            var isDebugEnabled = host.Logger.IsEnabled(LogEventLevel.Debug);
 3680            try
 3681            {
 1582                if (isDebugEnabled)
 3683                {
 384                    host.Logger.DebugSanitized("Preparing execution for C# script at {Path}", ctx.Request.Path);
 3685                }
 3686
 1587                var (Globals, Response, Context) = await DelegateBuilder.PrepareExecutionAsync(host, ctx, args).Configur
 3688
 3689                // Execute the script with the current context and shared state
 1590                if (isDebugEnabled)
 3691                {
 392                    host.Logger.DebugSanitized("Executing C# script for {Path}", ctx.Request.Path);
 3693                }
 3694
 1595                _ = await script.RunAsync(Globals).ConfigureAwait(false);
 1096                if (isDebugEnabled)
 3697                {
 398                    host.Logger.DebugSanitized("C# script executed successfully for {Path}", ctx.Request.Path);
 3699                }
 36100
 36101                // Apply the response to the Kestrun context
 10102                await DelegateBuilder.ApplyResponseAsync(ctx, Response, host.Logger).ConfigureAwait(false);
 10103            }
 36104            finally
 36105            {
 36106                // Intentionally do not call Response.CompleteAsync here to keep the pipeline open
 36107                // for middleware like StatusCodePages to generate bodies for status-only responses.
 36108            }
 46109        };
 110    }
 111
 112    /// <summary>
 113    /// Compiles the provided C# code into a script.
 114    /// This method supports additional imports and references, and can inject global variables into the script.
 115    /// It returns a compiled script that can be executed later.
 116    /// </summary>
 117    /// <param name="host">The Kestrun host instance.</param>
 118    /// <param name="code">The C# code to compile.</param>
 119    /// <param name="extraImports">Additional namespaces to import.</param>
 120    /// <param name="extraRefs">Additional assembly references.</param>
 121    /// <param name="locals">Local variables to inject into the script.</param>
 122    /// <param name="languageVersion">The C# language version to use.</param>
 123    /// <returns>A compiled script that can be executed later.</returns>
 124    /// <exception cref="ArgumentNullException">Thrown when the code is null or whitespace.</exception>
 125    /// <exception cref="CompilationErrorException">Thrown when there are compilation errors.</exception>
 126    /// <remarks>
 127    /// This method compiles the provided C# code into a script using Roslyn.
 128    /// It supports additional imports and references, and can inject global variables into the script.
 129    /// The script can be executed later with the provided globals and locals.
 130    /// It is useful for scenarios where dynamic C# code execution is required, such as in web applications or scripting
 131    /// </remarks>
 132    internal static Script<object> Compile(
 133            KestrunHost host,
 134            string? code, string[]? extraImports,
 135            Assembly[]? extraRefs, IReadOnlyDictionary<string, object?>? locals, LanguageVersion languageVersion = Langu
 136            )
 137    {
 59138        var log = host.Logger;
 59139        if (log.IsEnabled(LogEventLevel.Debug))
 140        {
 48141            log.Debug("Compiling C# script, length={Length}, imports={ImportsCount}, refs={RefsCount}, lang={Lang}",
 48142                code?.Length, extraImports?.Length ?? 0, extraRefs?.Length ?? 0, languageVersion);
 143        }
 144
 145        // Validate inputs
 59146        if (string.IsNullOrWhiteSpace(code))
 147        {
 0148            throw new ArgumentNullException(nameof(code), "C# code cannot be null or whitespace.");
 149        }
 150
 151        // References and imports
 59152        var coreRefs = DelegateBuilder.BuildBaselineReferences();
 153        // Core references + Kestrun + extras
 154        // Note: Order matters, Kestrun must come after core to avoid conflicts
 59155        var kestrunAssembly = typeof(KestrunHost).Assembly; // Kestrun.dll
 59156        var kestrunRef = MetadataReference.CreateFromFile(kestrunAssembly.Location);
 59157        var kestrunNamespaces = CollectKestrunNamespaces(kestrunAssembly);
 158        // Create script options
 59159        var opts = CreateScriptOptions(DelegateBuilder.PlatformImports, kestrunNamespaces, coreRefs, kestrunRef);
 59160        opts = AddExtraImports(opts, extraImports);
 59161        opts = AddExtraReferences(opts, extraRefs, log);
 162
 163        // Include currently loaded assemblies (deduplicated) to minimize missing reference issues.
 59164        opts = AddLoadedAssemblyReferences(opts, log);
 165
 166        // Globals/locals injection plus dynamic discovery of namespaces & assemblies needed
 59167        var (CodeWithPreamble, DynamicImports, DynamicReferences) = BuildGlobalsAndLocalsPreamble(host, code, locals);
 59168        code = CodeWithPreamble;
 169
 59170        if (DynamicImports.Count > 0)
 171        {
 22172            var newImports = DynamicImports.Except(opts.Imports, StringComparer.Ordinal).ToArray();
 22173            if (newImports.Length > 0)
 174            {
 0175                opts = opts.WithImports(opts.Imports.Concat(newImports));
 0176                if (log.IsEnabled(LogEventLevel.Debug))
 177                {
 0178                    log.Debug("Added {ImportCount} dynamic imports derived from globals/locals: {Imports}", newImports.L
 179                }
 180            }
 181        }
 182
 59183        if (DynamicReferences.Count > 0)
 184        {
 185            // Avoid duplicates by location
 22186            var existingRefPaths = new HashSet<string>(opts.MetadataReferences
 22187                .OfType<PortableExecutableReference>()
 5795188                .Select(r => r.FilePath ?? string.Empty)
 5817189                .Where(p => !string.IsNullOrEmpty(p)), StringComparer.OrdinalIgnoreCase);
 190
 22191            var newRefs = DynamicReferences
 22192                .Where(r => !string.IsNullOrEmpty(r.Location) && File.Exists(r.Location) && !existingRefPaths.Contains(r
 0193                .Select(r => MetadataReference.CreateFromFile(r.Location))
 22194                .ToArray();
 195
 22196            if (newRefs.Length > 0)
 197            {
 0198                opts = opts.WithReferences(opts.MetadataReferences.Concat(newRefs));
 0199                if (log.IsEnabled(LogEventLevel.Debug))
 200                {
 0201                    log.Debug("Added {RefCount} dynamic assembly reference(s) derived from globals/locals.", newRefs.Len
 202                }
 203            }
 204        }
 205
 206        // Compile
 59207        var script = CSharpScript.Create(code, opts, typeof(CsGlobals));
 59208        var diagnostics = CompileAndGetDiagnostics(script, log);
 59209        ThrowIfDiagnosticsNull(diagnostics);
 59210        ThrowOnErrors(diagnostics, log);
 58211        LogWarnings(diagnostics, log);
 58212        LogSuccessIfNoWarnings(diagnostics, log);
 213
 58214        return script;
 215    }
 216
 217    /// <summary>Collects metadata references for all non-dynamic loaded assemblies with a physical location.</summary>
 218    /// <param name="log">Logger.</param>
 219    /// <returns>Tuple of references and total count considered.</returns>
 220    private static (IEnumerable<MetadataReference> Refs, int Total) CollectLoadedAssemblyReferences(Serilog.ILogger log)
 221    {
 222        try
 223        {
 59224            var loaded = AppDomain.CurrentDomain.GetAssemblies();
 59225            var refs = new List<MetadataReference>(loaded.Length);
 59226            var considered = 0;
 36880227            foreach (var a in loaded)
 228            {
 18381229                considered++;
 18381230                if (a.IsDynamic)
 231                {
 232                    continue;
 233                }
 18149234                if (string.IsNullOrEmpty(a.Location) || !File.Exists(a.Location))
 235                {
 236                    continue;
 237                }
 238                try
 239                {
 15432240                    refs.Add(MetadataReference.CreateFromFile(a.Location));
 15432241                }
 0242                catch (Exception ex)
 243                {
 0244                    if (log.IsEnabled(LogEventLevel.Debug))
 245                    {
 0246                        log.Debug(ex, "Failed to add loaded assembly reference: {Assembly}", a.FullName);
 247                    }
 0248                }
 249            }
 59250            return (refs, considered);
 251        }
 0252        catch (Exception ex)
 253        {
 0254            log.Warning(ex, "Failed to enumerate loaded assemblies for dynamic references.");
 0255            return (Array.Empty<MetadataReference>(), 0);
 256        }
 59257    }
 258
 259    /// <summary>
 260    /// Builds the core assembly references for the script.
 261    /// </summary>
 262    /// <returns>The core assembly references.</returns>
 263
 264    /// <summary>
 265    /// Collects the namespaces from the Kestrun assembly.
 266    /// </summary>
 267    /// <param name="kestrunAssembly">The Kestrun assembly.</param>
 268    /// <returns>The collected namespaces.</returns>
 269    private static string[] CollectKestrunNamespaces(Assembly kestrunAssembly)
 270    {
 59271        return [.. kestrunAssembly
 59272            .GetExportedTypes()
 14042273            .Select(t => t.Namespace)
 14042274            .Where(ns => !string.IsNullOrEmpty(ns) && ns!.StartsWith("Kestrun", StringComparison.Ordinal))
 14042275            .Select(ns => ns!)
 59276            .Distinct()];
 277    }
 278
 279    /// <summary>
 280    /// Creates script options for the VB.NET script.
 281    /// </summary>
 282    /// <param name="platformImports">The platform-specific namespaces to import.</param>
 283    /// <param name="kestrunNamespaces">The Kestrun-specific namespaces to import.</param>
 284    /// <param name="coreRefs">The core assembly references to include.</param>
 285    /// <param name="kestrunRef">The Kestrun assembly reference to include.</param>
 286    /// <returns>The created script options.</returns>
 287    private static ScriptOptions CreateScriptOptions(
 288        IEnumerable<string> platformImports,
 289        IEnumerable<string> kestrunNamespaces,
 290        IEnumerable<MetadataReference> coreRefs,
 291        MetadataReference kestrunRef)
 292    {
 59293        var allImports = platformImports.Concat(kestrunNamespaces) ?? [];
 294        // Keep default references then add our core + Kestrun to avoid losing essential BCL assemblies
 59295        var opts = ScriptOptions.Default
 59296            .WithImports(allImports)
 59297            .AddReferences(coreRefs)
 59298            .AddReferences(kestrunRef);
 59299        return opts;
 300    }
 301
 302    /// <summary>
 303    /// Adds extra using directives to the script options.
 304    /// </summary>
 305    /// <param name="opts">The script options to modify.</param>
 306    /// <param name="extraImports">The extra using directives to add.</param>
 307    /// <returns>The modified script options.</returns>
 308    private static ScriptOptions AddExtraImports(ScriptOptions opts, string[]? extraImports)
 309    {
 59310        extraImports ??= ["Kestrun"];
 59311        if (!extraImports.Contains("Kestrun"))
 312        {
 1313            var importsList = extraImports.ToList();
 1314            importsList.Add("Kestrun");
 1315            extraImports = [.. importsList];
 316        }
 59317        return extraImports.Length > 0
 59318            ? opts.WithImports(opts.Imports.Concat(extraImports))
 59319            : opts;
 320    }
 321
 322    /// <summary>
 323    /// Adds extra assembly references to the script options.
 324    /// </summary>
 325    /// <param name="opts">The script options to modify.</param>
 326    /// <param name="extraRefs">The extra assembly references to add.</param>
 327    /// <param name="log">The logger to use for logging.</param>
 328    /// <returns>The modified script options.</returns>
 329    private static ScriptOptions AddExtraReferences(ScriptOptions opts, Assembly[]? extraRefs, Serilog.ILogger log)
 330    {
 59331        if (extraRefs is not { Length: > 0 })
 332        {
 59333            return opts;
 334        }
 335
 0336        foreach (var r in extraRefs)
 337        {
 0338            if (string.IsNullOrEmpty(r.Location))
 339            {
 0340                log.Warning("Skipping dynamic assembly with no location: {Assembly}", r.FullName);
 341            }
 0342            else if (!File.Exists(r.Location))
 343            {
 0344                log.Warning("Skipping missing assembly file: {Location}", r.Location);
 345            }
 346        }
 347
 0348        var safeRefs = extraRefs
 0349            .Where(r => !string.IsNullOrEmpty(r.Location) && File.Exists(r.Location))
 0350            .Select(r => MetadataReference.CreateFromFile(r.Location));
 351
 0352        return opts.WithReferences(opts.MetadataReferences.Concat(safeRefs));
 353    }
 354
 355    /// <summary>
 356    /// Adds references for all currently loaded (non-duplicate) assemblies to the script options.
 357    /// </summary>
 358    /// <param name="opts">Current script options.</param>
 359    /// <param name="log">Logger.</param>
 360    /// <returns>Updated script options.</returns>
 361    private static ScriptOptions AddLoadedAssemblyReferences(ScriptOptions opts, Serilog.ILogger log)
 362    {
 363        // Optionally include all currently loaded assemblies to reduce missing reference issues.
 364        // Roslyn will de-duplicate by file path internally but we still filter to avoid redundant work.
 59365        var (loadedRefs, loadedCount) = CollectLoadedAssemblyReferences(log);
 59366        if (loadedCount <= 0)
 367        {
 0368            return opts;
 369        }
 370
 59371        var existingPaths = new HashSet<string>(opts.MetadataReferences
 59372            .OfType<PortableExecutableReference>()
 11004373            .Select(r => r.FilePath ?? string.Empty)
 11063374            .Where(p => !string.IsNullOrEmpty(p)), StringComparer.OrdinalIgnoreCase);
 375
 59376        var newLoadedRefs = loadedRefs
 15432377            .Where(r => r is PortableExecutableReference pe && !string.IsNullOrEmpty(pe.FilePath) && !existingPaths.Cont
 59378            .ToArray();
 379
 59380        if (newLoadedRefs.Length == 0)
 381        {
 0382            return opts;
 383        }
 384
 59385        var updated = opts.WithReferences(opts.MetadataReferences.Concat(newLoadedRefs));
 59386        if (log.IsEnabled(LogEventLevel.Debug))
 387        {
 48388            log.Debug("Added {RefCount} loaded assembly reference(s) (of {TotalLoaded}) for dynamic script compilation."
 389        }
 59390        return updated;
 391    }
 392
 393    /// <summary>
 394    /// Prepends global and local variable declarations to the provided code.
 395    /// </summary>
 396    ///<param name="host">The Kestrun host instance.</param>
 397    /// <param name="code">The original code to modify.</param>
 398    /// <param name="locals">The local variables to include.</param>
 399    /// <returns>The modified code with global and local variable declarations.</returns>
 400    /// <summary>Builds the preamble variable declarations for globals &amp; locals and discovers required namespaces an
 401    /// <returns>Tuple containing code with preamble, dynamic imports, dynamic references.</returns>
 402    private static (string CodeWithPreamble, List<string> DynamicImports, List<Assembly> DynamicReferences) BuildGlobals
 403        KestrunHost host,
 404        string? code,
 405        IReadOnlyDictionary<string, object?>? locals)
 406    {
 407        // Merge globals + locals
 59408        var merged = MergeGlobalsAndLocals(host, locals);
 409
 410        // Build preamble & discover dynamic imports/refs
 59411        var (preamble, imports, refs) = GeneratePreambleAndDiscover(merged);
 412
 413        // Append original code
 59414        var finalCode = preamble.Length > 0 ? preamble + (code ?? string.Empty) : code ?? string.Empty;
 415
 416        // Filter references to only those with locations
 81417        var filteredRefs = refs.Where(r => !string.IsNullOrEmpty(r.Location)).ToList();
 418
 59419        LogDynamicDiscovery(imports, filteredRefs, host.Logger);
 420
 59421        return (finalCode, imports.ToList(), filteredRefs);
 422    }
 423
 424    /// <summary>
 425    /// Creates a merged dictionary of global shared state and the supplied <paramref name="locals"/>.
 426    /// Local values override globals when a key collision occurs (case-insensitive).
 427    /// </summary>
 428    /// <param name="host">The Kestrun host instance.</param>
 429    /// <param name="locals">Optional locals dictionary passed in at compile time.</param>
 430    /// <returns>A mutable dictionary keyed by variable name mapping to its source store name and value.</returns>
 431    private static Dictionary<string, (string Dict, object? Value)> MergeGlobalsAndLocals(
 432        KestrunHost host,
 433        IReadOnlyDictionary<string, object?>? locals)
 434    {
 59435        var merged = new Dictionary<string, (string Dict, object? Value)>(StringComparer.OrdinalIgnoreCase);
 118436        foreach (var g in GlobalStore.Snapshot())
 437        {
 0438            merged[g.Key] = ("Globals", g.Value);
 439        }
 440        // Also include host-level shared state
 441        // if host shared state has same key as global, host takes precedence
 126442        foreach (var g in host.SharedState.Snapshot())
 443        {
 4444            merged[g.Key] = ("Globals", g.Value);
 445        }
 446        // Now overlay locals
 447        // if local has same key as global, local takes precedence
 59448        if (locals is { Count: > 0 })
 449        {
 80450            foreach (var l in locals)
 451            {
 21452                merged[l.Key] = ("Locals", l.Value);
 453            }
 454        }
 59455        return merged;
 456    }
 457
 458    /// <summary>
 459    /// Iterates all merged global + local variables and builds a textual preamble of variable declarations.
 460    /// While building, collects the required namespace imports and assembly references inferred from the runtime value 
 461    /// </summary>
 462    /// <param name="merged">Merged globals + locals produced by <see cref="MergeGlobalsAndLocals"/>.</param>
 463    /// <returns>Tuple containing the preamble text builder, discovered namespace import set, and assembly reference set
 464    private static (StringBuilder Preamble, HashSet<string> Imports, HashSet<Assembly> Refs) GeneratePreambleAndDiscover
 465        Dictionary<string, (string Dict, object? Value)> merged)
 466    {
 59467        var preambleBuilder = new StringBuilder();
 59468        var dynamicImports = new HashSet<string>(StringComparer.Ordinal);
 59469        var dynamicRefs = new HashSet<Assembly>();
 470
 166471        foreach (var kvp in merged)
 472        {
 24473            AppendVariableDeclarationAndCollect(kvp, preambleBuilder, dynamicImports, dynamicRefs);
 474        }
 475
 59476        return (preambleBuilder, dynamicImports, dynamicRefs);
 477    }
 478
 479    /// <summary>
 480    /// Appends a single variable declaration for the provided key/value and gathers any dynamic imports &amp; reference
 481    /// </summary>
 482    /// <param name="kvp">The merged key and its (source dictionary name, value).</param>
 483    /// <param name="preambleBuilder">Builder accumulating declaration lines.</param>
 484    /// <param name="dynamicImports">Set capturing namespaces to import.</param>
 485    /// <param name="dynamicRefs">Set capturing assemblies that must be referenced.</param>
 486    private static void AppendVariableDeclarationAndCollect(
 487        KeyValuePair<string, (string Dict, object? Value)> kvp,
 488        StringBuilder preambleBuilder,
 489        HashSet<string> dynamicImports,
 490        HashSet<Assembly> dynamicRefs)
 491    {
 24492        var valueType = kvp.Value.Value?.GetType();
 24493        var typeName = FormatTypeName(valueType);
 24494        _ = preambleBuilder.AppendLine($"var {kvp.Key} = ({typeName}){kvp.Value.Dict}[\"{kvp.Key}\"]; ");
 495
 24496        if (valueType == null)
 497        {
 0498            return;
 499        }
 500
 24501        TryAddNamespace(dynamicImports, valueType.Namespace);
 24502        CollectGenericArguments(valueType, dynamicImports, dynamicRefs);
 24503        _ = dynamicRefs.Add(valueType.Assembly);
 24504    }
 505
 506    /// <summary>
 507    /// Adds a namespace string to the imports set if it is non-empty.
 508    /// </summary>
 509    /// <param name="imports">Namespace accumulator set.</param>
 510    /// <param name="ns">Namespace candidate.</param>
 511    private static void TryAddNamespace(HashSet<string> imports, string? ns)
 512    {
 27513        if (!string.IsNullOrEmpty(ns))
 514        {
 27515            _ = imports.Add(ns);
 516        }
 27517    }
 518
 519    /// <summary>
 520    /// For a generic type, collects namespaces and assemblies for each generic argument.
 521    /// </summary>
 522    /// <param name="valueType">The possibly generic value type.</param>
 523    /// <param name="imports">Namespace accumulator set.</param>
 524    /// <param name="refs">Assembly reference accumulator set.</param>
 525    private static void CollectGenericArguments(Type valueType, HashSet<string> imports, HashSet<Assembly> refs)
 526    {
 24527        if (!valueType.IsGenericType)
 528        {
 22529            return;
 530        }
 10531        foreach (var ga in valueType.GetGenericArguments())
 532        {
 3533            TryAddNamespace(imports, ga.Namespace);
 3534            _ = refs.Add(ga.Assembly);
 535        }
 2536    }
 537
 538    /// <summary>
 539    /// Emits a debug log summarizing the dynamic imports and assembly references that were discovered.
 540    /// </summary>
 541    /// <param name="imports">Collected imports.</param>
 542    /// <param name="refs">Collected assembly references (filtered to those with physical locations).</param>
 543    /// <param name="log">Logger.</param>
 544    private static void LogDynamicDiscovery(HashSet<string> imports, List<Assembly> refs, Serilog.ILogger log)
 545    {
 59546        if (log.IsEnabled(LogEventLevel.Debug) && (imports.Count > 0 || refs.Count > 0))
 547        {
 22548            log.Debug("Discovered {ImportCount} dynamic import(s) and {RefCount} reference(s) from globals/locals.", imp
 549        }
 59550    }
 551
 552    // Produces a C# friendly type name for reflection types (handles generics, arrays, nullable, and fallbacks).
 553    private static string FormatTypeName(Type? t)
 554    {
 28555        if (t == null)
 556        {
 0557            return "object";
 558        }
 28559        if (t.IsGenericParameter)
 560        {
 0561            return "object";
 562        }
 28563        if (t.IsArray)
 564        {
 1565            return FormatTypeName(t.GetElementType()) + "[]";
 566        }
 567        // Nullable<T>
 27568        if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
 569        {
 0570            return FormatTypeName(t.GetGenericArguments()[0]) + "?";
 571        }
 27572        if (t.IsGenericType)
 573        {
 574            try
 575            {
 2576                var genericDefName = t.Name;
 2577                var tickIndex = genericDefName.IndexOf('`');
 2578                if (tickIndex > 0)
 579                {
 2580                    genericDefName = genericDefName[..tickIndex];
 581                }
 2582                var args = t.GetGenericArguments().Select(FormatTypeName);
 2583                return (t.Namespace != null ? t.Namespace + "." : string.Empty) + genericDefName + "<" + string.Join(","
 584            }
 0585            catch
 586            {
 0587                return "object";
 588            }
 589        }
 590        // Non generic
 25591        return t.FullName ?? t.Name ?? "object";
 2592    }
 593
 594    /// <summary>
 595    /// Compiles the provided VB.NET script and returns any diagnostics.
 596    /// </summary>
 597    /// <param name="script">The VB.NET script to compile.</param>
 598    /// <param name="log">The logger to use for logging.</param>
 599    /// <returns>A collection of diagnostics produced during compilation, or null if compilation failed.</returns>
 600    private static ImmutableArray<Diagnostic>? CompileAndGetDiagnostics(Script<object> script, Serilog.ILogger log)
 601    {
 602        try
 603        {
 59604            return script.Compile();
 605        }
 0606        catch (CompilationErrorException ex)
 607        {
 0608            log.Error(ex, "C# script compilation failed with errors.");
 0609            return null;
 610        }
 59611    }
 612
 613    private static void ThrowIfDiagnosticsNull(ImmutableArray<Diagnostic>? diagnostics)
 614    {
 59615        if (diagnostics == null)
 616        {
 0617            throw new CompilationErrorException("C# script compilation failed with no diagnostics.", []);
 618        }
 59619    }
 620
 621    /// <summary>
 622    /// Throws a CompilationErrorException if the diagnostics are null.
 623    /// </summary>
 624    /// <param name="diagnostics">The compilation diagnostics.</param>
 625    /// <param name="log">The logger to use for logging.</param>
 626    /// <exception cref="CompilationErrorException"></exception>
 627    private static void ThrowOnErrors(ImmutableArray<Diagnostic>? diagnostics, Serilog.ILogger log)
 628    {
 60629        if (diagnostics?.Any(d => d.Severity == DiagnosticSeverity.Error) != true)
 630        {
 58631            return;
 632        }
 633
 2634        var errors = diagnostics?.Where(d => d.Severity == DiagnosticSeverity.Error).ToArray();
 1635        if (errors is not { Length: > 0 })
 636        {
 0637            return;
 638        }
 639
 1640        var sb = new StringBuilder();
 1641        _ = sb.AppendLine($"C# script compilation completed with {errors.Length} error(s):");
 4642        foreach (var error in errors)
 643        {
 1644            var location = error.Location.IsInSource
 1645                ? $" at line {error.Location.GetLineSpan().StartLinePosition.Line + 1}"
 1646                : string.Empty;
 1647            var msg = $"  Error [{error.Id}]: {error.GetMessage()}{location}";
 1648            log.Error(msg);
 1649            _ = sb.AppendLine(msg);
 650        }
 1651        throw new CompilationErrorException("C# route code compilation failed\n" + sb.ToString(), diagnostics ?? []);
 652    }
 653
 654    /// <summary>
 655    /// Logs warning messages if the compilation succeeded with warnings.
 656    /// </summary>
 657    /// <param name="diagnostics">The compilation diagnostics.</param>
 658    /// <param name="log">The logger to use for logging.</param>
 659    private static void LogWarnings(ImmutableArray<Diagnostic>? diagnostics, Serilog.ILogger log)
 660    {
 58661        var warnings = diagnostics?.Where(d => d.Severity == DiagnosticSeverity.Warning).ToArray();
 58662        if (warnings is not null && warnings.Length != 0)
 663        {
 0664            log.Warning($"C# script compilation completed with {warnings.Length} warning(s):");
 0665            foreach (var warning in warnings)
 666            {
 0667                var location = warning.Location.IsInSource
 0668                    ? $" at line {warning.Location.GetLineSpan().StartLinePosition.Line + 1}"
 0669                    : string.Empty;
 0670                log.Warning($"  Warning [{warning.Id}]: {warning.GetMessage()}{location}");
 671            }
 672        }
 58673    }
 674
 675    /// <summary>
 676    /// Logs a success message if the compilation succeeded without warnings.
 677    /// </summary>
 678    /// <param name="diagnostics">The compilation diagnostics.</param>
 679    /// <param name="log">The logger to use for logging.</param>
 680    private static void LogSuccessIfNoWarnings(ImmutableArray<Diagnostic>? diagnostics, Serilog.ILogger log)
 681    {
 58682        var warnings = diagnostics?.Where(d => d.Severity == DiagnosticSeverity.Warning).ToArray();
 58683        if (warnings != null && warnings.Length == 0 && log.IsEnabled(LogEventLevel.Debug))
 684        {
 47685            log.Debug("C# script compiled successfully with no warnings.");
 686        }
 58687    }
 688}

Methods/Properties

Build(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.String[],System.Reflection.Assembly[],Microsoft.CodeAnalysis.CSharp.LanguageVersion)
BuildExecutionDelegate(Kestrun.Hosting.KestrunHost,Microsoft.CodeAnalysis.Scripting.Script`1<System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
Compile(Kestrun.Hosting.KestrunHost,System.String,System.String[],System.Reflection.Assembly[],System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>,Microsoft.CodeAnalysis.CSharp.LanguageVersion)
CollectLoadedAssemblyReferences(Serilog.ILogger)
CollectKestrunNamespaces(System.Reflection.Assembly)
CreateScriptOptions(System.Collections.Generic.IEnumerable`1<System.String>,System.Collections.Generic.IEnumerable`1<System.String>,System.Collections.Generic.IEnumerable`1<Microsoft.CodeAnalysis.MetadataReference>,Microsoft.CodeAnalysis.MetadataReference)
AddExtraImports(Microsoft.CodeAnalysis.Scripting.ScriptOptions,System.String[])
AddExtraReferences(Microsoft.CodeAnalysis.Scripting.ScriptOptions,System.Reflection.Assembly[],Serilog.ILogger)
AddLoadedAssemblyReferences(Microsoft.CodeAnalysis.Scripting.ScriptOptions,Serilog.ILogger)
BuildGlobalsAndLocalsPreamble(Kestrun.Hosting.KestrunHost,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>)
MergeGlobalsAndLocals(Kestrun.Hosting.KestrunHost,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>)
GeneratePreambleAndDiscover(System.Collections.Generic.Dictionary`2<System.String,System.ValueTuple`2<System.String,System.Object>>)
AppendVariableDeclarationAndCollect(System.Collections.Generic.KeyValuePair`2<System.String,System.ValueTuple`2<System.String,System.Object>>,System.Text.StringBuilder,System.Collections.Generic.HashSet`1<System.String>,System.Collections.Generic.HashSet`1<System.Reflection.Assembly>)
TryAddNamespace(System.Collections.Generic.HashSet`1<System.String>,System.String)
CollectGenericArguments(System.Type,System.Collections.Generic.HashSet`1<System.String>,System.Collections.Generic.HashSet`1<System.Reflection.Assembly>)
LogDynamicDiscovery(System.Collections.Generic.HashSet`1<System.String>,System.Collections.Generic.List`1<System.Reflection.Assembly>,Serilog.ILogger)
FormatTypeName(System.Type)
CompileAndGetDiagnostics(Microsoft.CodeAnalysis.Scripting.Script`1<System.Object>,Serilog.ILogger)
ThrowIfDiagnosticsNull(System.Nullable`1<System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.Diagnostic>>)
ThrowOnErrors(System.Nullable`1<System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.Diagnostic>>,Serilog.ILogger)
LogWarnings(System.Nullable`1<System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.Diagnostic>>,Serilog.ILogger)
LogSuccessIfNoWarnings(System.Nullable`1<System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.Diagnostic>>,Serilog.ILogger)