8e3bbf91fb
R04:P2 - every analysis derives from one process-wide bound base compilation via ReplaceSyntaxTree (the scoped service keeps its current compilation); every entry point takes the request token, the endpoints bind RequestAborted and answer 499 on abort, and the Monaco providers abort their fetch when Monaco cancels the request. R04:P3 - the catalog snapshot carries a sorted, de-duplicated string[] (binary-search prefix completion) and an Ordinal path->info dictionary, built once per snapshot. R04:S1 - Diagnose emits an OTSCRIPT_SETVTAG_INERT warning for each ctx.SetVirtualTag call. It is a warning, so editor-accepts == publish-accepts still holds. Refs #745 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
59 lines
2.7 KiB
C#
59 lines
2.7 KiB
C#
using Microsoft.CodeAnalysis;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.AdminUI.ScriptAnalysis;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.ScriptAnalysis;
|
|
|
|
/// <summary>
|
|
/// R04:P2 — the editor used to build a brand-new <c>CSharpCompilation</c> (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 <c>ReplaceSyntaxTree</c>, so the referenced-assembly
|
|
/// binding is done once and shared.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|