using Microsoft.CodeAnalysis; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.ScriptAnalysis; /// /// R04:P2 — the editor used to build a brand-new CSharpCompilation (and re-bind the whole sandbox /// reference set) on every completion / hover / signature-help / diagnostics request. Each analysis now /// derives from ONE process-wide base compilation via ReplaceSyntaxTree, so the referenced-assembly /// binding is done once and shared. /// public sealed class AnalysisCompilationReuseTests { [Fact] public void Two_services_share_the_bound_reference_set() { // Two separate scopes (two HTTP requests) analysing different sources. var c1 = new ScriptAnalysisService().CompilationFor("return 1;"); var c2 = new ScriptAnalysisService().CompilationFor("return 2;"); // Positive control: the two compilations really carry different user sources. c1.SyntaxTrees.Single().ToString().ShouldContain("return 1;"); c2.SyntaxTrees.Single().ToString().ShouldContain("return 2;"); c1.ShouldNotBeSameAs(c2); // Derived from one bound base: the corlib symbol is the SAME instance, not a re-bound copy. c1.GetSpecialType(SpecialType.System_Object) .ShouldBeSameAs(c2.GetSpecialType(SpecialType.System_Object)); c1.GetSpecialType(SpecialType.System_Object) .ShouldBeSameAs(ScriptAnalysisService.BaseCompilation.GetSpecialType(SpecialType.System_Object)); } [Fact] public void Repeated_analysis_in_one_scope_replaces_the_tree_rather_than_adding_one() { var svc = new ScriptAnalysisService(); svc.CompilationFor("return 1;"); var second = svc.CompilationFor("return 2;"); second.SyntaxTrees.Count().ShouldBe(1); second.SyntaxTrees.Single().ToString().ShouldContain("return 2;"); second.SyntaxTrees.Single().ToString().ShouldNotContain("return 1;"); } [Fact] public void Diagnostics_are_unchanged_by_reuse() { // A clean script stays clean and an error stays an error across interleaved analyses on one service // — a stale tree leaking from the previous call would surface here. var svc = new ScriptAnalysisService(); svc.Diagnose(new DiagnoseRequest("return ctx.Nope();")).Markers.ShouldContain(m => m.Severity == 8); svc.Diagnose(new DiagnoseRequest("""return (double)ctx.GetTag("A").Value * 2.0;""")).Markers.ShouldBeEmpty(); svc.Diagnose(new DiagnoseRequest("var a = 1;\nreturn ctx.Nope();")).Markers .ShouldContain(m => m.Severity == 8 && m.StartLineNumber == 2); } }