feat(scriptanalysis): M3.1 shared trust validator + compiler + compile surfaces + tests
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Scripting;
|
||||
using Microsoft.CodeAnalysis.Scripting;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.ScriptAnalysis;
|
||||
|
||||
/// <summary>
|
||||
/// M3.1: the single authoritative Roslyn compile gate. Ported from the
|
||||
/// SiteRuntime <c>ScriptCompilationService.CompileCore</c>, but returns
|
||||
/// diagnostic messages rather than a compiled <c>Script</c> delegate — this is
|
||||
/// the design-time gate (the deploy-time validation that previously relied on
|
||||
/// the FAKE substring + brace-balance check in
|
||||
/// <c>TemplateEngine/Validation/ScriptCompiler.cs</c>), which needs to know
|
||||
/// whether the script <em>compiles</em>, not to execute it.
|
||||
/// </summary>
|
||||
public static class RoslynScriptCompiler
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the script as C# script source and returns syntax-error diagnostic
|
||||
/// messages (severity Error only). Empty list means the script parses.
|
||||
/// </summary>
|
||||
/// <param name="code">The C# script source to parse.</param>
|
||||
/// <returns>Error-severity parse diagnostic messages; empty if the script parses.</returns>
|
||||
public static IReadOnlyList<string> ParseDiagnostics(string code)
|
||||
{
|
||||
var tree = CSharpSyntaxTree.ParseText(
|
||||
code, new CSharpParseOptions(kind: SourceCodeKind.Script));
|
||||
|
||||
return tree.GetDiagnostics()
|
||||
.Where(d => d.Severity == DiagnosticSeverity.Error)
|
||||
.Select(d => d.GetMessage())
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles the script against the trust-model references and imports and
|
||||
/// returns error-severity compilation diagnostic messages. Empty list means
|
||||
/// the script compiles cleanly against <paramref name="globalsType"/>.
|
||||
/// </summary>
|
||||
/// <param name="code">The C# script source to compile.</param>
|
||||
/// <param name="globalsType">
|
||||
/// Optional globals type the script binds against — e.g.
|
||||
/// <c>ScriptCompileSurface</c> for instance/shared scripts or
|
||||
/// <c>TriggerCompileSurface</c> for trigger expressions.
|
||||
/// </param>
|
||||
/// <param name="extraReferences">Optional additional metadata references.</param>
|
||||
/// <param name="extraImports">Optional additional namespace imports.</param>
|
||||
/// <returns>Error-severity compile diagnostic messages; empty if the script compiles.</returns>
|
||||
public static IReadOnlyList<string> Compile(
|
||||
string code,
|
||||
Type? globalsType = null,
|
||||
IEnumerable<MetadataReference>? extraReferences = null,
|
||||
IEnumerable<string>? extraImports = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var references = ScriptTrustPolicy.DefaultReferences.ToList();
|
||||
if (extraReferences != null)
|
||||
references.AddRange(extraReferences);
|
||||
|
||||
var imports = ScriptTrustPolicy.DefaultImports.AsEnumerable();
|
||||
if (extraImports != null)
|
||||
imports = imports.Concat(extraImports);
|
||||
|
||||
var options = ScriptOptions.Default
|
||||
.WithReferences(references)
|
||||
.WithImports(imports);
|
||||
|
||||
var script = CSharpScript.Create<object?>(code, options, globalsType);
|
||||
var diagnostics = script.Compile();
|
||||
|
||||
return diagnostics
|
||||
.Where(d => d.Severity == DiagnosticSeverity.Error)
|
||||
.Select(d => d.GetMessage())
|
||||
.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return [$"Compilation exception: {ex.Message}"];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user