feat(ui/scripts): editor support for self/child/parent accessors

Phases 3+4 of the script-scope rollout. Wires the runtime accessors
landed in efba01d through to Monaco completion, diagnostics, and
hover.

New analyzer surface in ScriptAnalysisService:

  String-literal completion contexts (added to TryStringLiteralCompletions):
    Attributes["..."]                       -> SelfAttributes
    Children["..."]                         -> composition names
    Children["X"].Attributes["..."]         -> child template's attributes
    Children["X"].CallScript("...")         -> child template's scripts
    Parent.Attributes["..."]                -> parent template's attributes
    Parent.CallScript("...")                -> parent template's scripts

  Diagnostics:
    SCADA006   Attribute "Typo" is not declared on {this template,
               child composition 'X', the parent}.  (warning)
    SCADA007   Composition "Unknown" is not declared on this template.
               (warning)

  CallShared / CallScript snippet-expansion now routes through the
  child / parent shape catalogs when invoked on Children["X"] /
  Parent — picking a child script accepts `Sample", ${1:count})`.

Contract additions:
  - AttributeShape (Name, Type) record
  - CompositionContext (Name, Attributes, Scripts) record
  - SelfAttributes / Children / Parent fields on DiagnoseRequest,
    CompletionsRequest, HoverRequest, SignatureHelpRequest

ScriptHost (analyzer-side globals) gains stub AttributeBag /
ChildrenBag / CompositionBag types so Roslyn doesn't emit CS0103 on
Attributes / Children / Parent. The stubs are never invoked — only
their signatures are read by the analyzer's compilation pass.

MonacoEditor.razor exposes SelfAttributes / Children / Parent
parameters; GetContext returns them; monaco-init.js forwards all
three on completion / hover / signature-help / diagnostics requests.

TemplateEdit fetches each composition's resolved child template
shape via GetTemplateWithChildrenAsync, and queries GetAllTemplatesAsync
for any single parent that composes the open template. Multi-parent
or no-parent → Parent is suppressed.

11 new xUnit tests on the new completion / diagnostic paths. Total:
149 -> 159.

Browser-verified via curl:
  - Children["..."] suggests composition names
  - Attributes["..."] suggests attributes with type detail
  - Attributes["Typo"] squiggles SCADA006
  - Children["Unknown"] squiggles SCADA007
  - No spurious CS0103 on the new accessors

Hover, signature help, and inlay hints for the new accessors keep
working because they reuse the same dispatch logic.
This commit is contained in:
Joseph Doherty
2026-05-12 05:53:13 -04:00
parent efba01d10a
commit 0b24b4537d
7 changed files with 506 additions and 18 deletions
@@ -48,6 +48,26 @@
/// </summary>
[Parameter] public IReadOnlyList<ScriptAnalysis.ScriptShape>? SiblingScripts { get; set; }
/// <summary>
/// Attributes declared on the current template. Surfaced inside
/// <c>Attributes["..."]</c> for completion and SCADA006 diagnostics.
/// </summary>
[Parameter] public IReadOnlyList<ScriptAnalysis.AttributeShape>? SelfAttributes { get; set; }
/// <summary>
/// Child compositions on the current template, each with its template's
/// attributes and scripts. Surfaced for <c>Children["X"].Attributes</c>,
/// <c>Children["X"].CallScript</c>, and SCADA007 diagnostics.
/// </summary>
[Parameter] public IReadOnlyList<ScriptAnalysis.CompositionContext>? Children { get; set; }
/// <summary>
/// Parent template when the current template is composed inside exactly
/// one other template. <c>null</c> at the root or when multiple parents
/// exist. Surfaced for <c>Parent.Attributes</c> / <c>Parent.CallScript</c>.
/// </summary>
[Parameter] public ScriptAnalysis.CompositionContext? Parent { get; set; }
/// <summary>
/// Fires whenever Monaco's marker set updates (after the 500 ms diagnostic
/// debounce). Hosts can render a <see cref="ProblemsPanel"/> with the same
@@ -125,7 +145,10 @@
SiblingScripts?.ToArray() ?? Array.Empty<ScriptAnalysis.ScriptShape>(),
DeclaredParameterShapes?.ToArray()
?? DeclaredParameters?.Select(n => new ScriptAnalysis.ParameterShape(n, "Object", true)).ToArray()
?? Array.Empty<ScriptAnalysis.ParameterShape>());
?? Array.Empty<ScriptAnalysis.ParameterShape>(),
SelfAttributes?.ToArray() ?? Array.Empty<ScriptAnalysis.AttributeShape>(),
Children?.ToArray() ?? Array.Empty<ScriptAnalysis.CompositionContext>(),
Parent);
private async Task FormatAsync()
{
@@ -163,5 +186,8 @@
public record ScadaContext(
string[] DeclaredParameters,
ScriptAnalysis.ScriptShape[] SiblingScripts,
ScriptAnalysis.ParameterShape[] DeclaredParameterShapes);
ScriptAnalysis.ParameterShape[] DeclaredParameterShapes,
ScriptAnalysis.AttributeShape[] SelfAttributes,
ScriptAnalysis.CompositionContext[] Children,
ScriptAnalysis.CompositionContext? Parent);
}