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:
@@ -92,6 +92,9 @@
|
||||
private MonacoEditor? _scriptEditor;
|
||||
private IReadOnlyList<ScadaLink.CentralUI.ScriptAnalysis.DiagnosticMarker> _scriptMarkers
|
||||
= Array.Empty<ScadaLink.CentralUI.ScriptAnalysis.DiagnosticMarker>();
|
||||
private IReadOnlyList<ScadaLink.CentralUI.ScriptAnalysis.CompositionContext> _editorChildren
|
||||
= Array.Empty<ScadaLink.CentralUI.ScriptAnalysis.CompositionContext>();
|
||||
private ScadaLink.CentralUI.ScriptAnalysis.CompositionContext? _editorParent;
|
||||
|
||||
private bool _showCompForm;
|
||||
private int _compComposedTemplateId;
|
||||
@@ -126,6 +129,13 @@
|
||||
_scripts = (await TemplateEngineRepository.GetScriptsByTemplateIdAsync(Id)).ToList();
|
||||
_compositions = (await TemplateEngineRepository.GetCompositionsByTemplateIdAsync(Id)).ToList();
|
||||
|
||||
// Editor metadata: child compositions + parent (if exactly one).
|
||||
// Powers Attributes["X"] / Children["Y"].Attributes["Z"] /
|
||||
// Parent.Attributes["W"] completion + SCADA006 / SCADA007 diagnostics
|
||||
// in the Monaco editor.
|
||||
_editorChildren = await BuildChildContextsAsync(_compositions);
|
||||
_editorParent = await TryGetParentContextAsync(Id);
|
||||
|
||||
_validationResult = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -662,6 +672,9 @@
|
||||
DeclaredParameters="@ScriptParameterNames.Parse(_scriptParameters)"
|
||||
DeclaredParameterShapes="@ScriptParameterNames.ParseShapes(_scriptParameters)"
|
||||
SiblingScripts="@(_scripts.Select(s => ScadaLink.CentralUI.ScriptAnalysis.ScriptShapeParser.Parse(s.Name, s.ParameterDefinitions, s.ReturnDefinition)).ToArray())"
|
||||
SelfAttributes="@(_attributes.Select(a => new ScadaLink.CentralUI.ScriptAnalysis.AttributeShape(a.Name, MapDataType(a.DataType))).ToArray())"
|
||||
Children="@_editorChildren"
|
||||
Parent="@_editorParent"
|
||||
MarkersChanged="@(m => { _scriptMarkers = m; StateHasChanged(); })" />
|
||||
<ProblemsPanel Markers="@_scriptMarkers" OnNavigate="@(m => _scriptEditor?.RevealLineAsync(m.StartLineNumber, m.StartColumn) ?? Task.CompletedTask)" />
|
||||
</div>
|
||||
@@ -970,4 +983,54 @@
|
||||
}
|
||||
else { _toast.ShowError(result.Error); }
|
||||
}
|
||||
|
||||
// ---- Editor metadata builders ----
|
||||
|
||||
private async Task<IReadOnlyList<ScadaLink.CentralUI.ScriptAnalysis.CompositionContext>> BuildChildContextsAsync(
|
||||
IReadOnlyList<ScadaLink.Commons.Entities.Templates.TemplateComposition> comps)
|
||||
{
|
||||
var result = new List<ScadaLink.CentralUI.ScriptAnalysis.CompositionContext>();
|
||||
foreach (var comp in comps)
|
||||
{
|
||||
var composed = await TemplateEngineRepository.GetTemplateWithChildrenAsync(comp.ComposedTemplateId);
|
||||
if (composed == null) continue;
|
||||
result.Add(BuildCompositionContext(comp.InstanceName, composed));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<ScadaLink.CentralUI.ScriptAnalysis.CompositionContext?> TryGetParentContextAsync(int templateId)
|
||||
{
|
||||
var all = await TemplateEngineRepository.GetAllTemplatesAsync();
|
||||
var parents = all.Where(t => t.Compositions.Any(c => c.ComposedTemplateId == templateId)).ToList();
|
||||
if (parents.Count != 1) return null; // ambiguous or root — suppress Parent assistance
|
||||
var p = await TemplateEngineRepository.GetTemplateWithChildrenAsync(parents[0].Id);
|
||||
return p == null ? null : BuildCompositionContext(p.Name, p);
|
||||
}
|
||||
|
||||
private static ScadaLink.CentralUI.ScriptAnalysis.CompositionContext BuildCompositionContext(
|
||||
string label,
|
||||
ScadaLink.Commons.Entities.Templates.Template t)
|
||||
{
|
||||
var attrs = t.Attributes
|
||||
.Select(a => new ScadaLink.CentralUI.ScriptAnalysis.AttributeShape(a.Name, MapDataType(a.DataType)))
|
||||
.ToList();
|
||||
var scripts = t.Scripts
|
||||
.Select(s => ScadaLink.CentralUI.ScriptAnalysis.ScriptShapeParser.Parse(
|
||||
s.Name, s.ParameterDefinitions, s.ReturnDefinition))
|
||||
.ToList();
|
||||
return new ScadaLink.CentralUI.ScriptAnalysis.CompositionContext(label, attrs, scripts);
|
||||
}
|
||||
|
||||
private static string MapDataType(ScadaLink.Commons.Types.Enums.DataType dt) => dt switch
|
||||
{
|
||||
ScadaLink.Commons.Types.Enums.DataType.Boolean => "Boolean",
|
||||
ScadaLink.Commons.Types.Enums.DataType.Int32 => "Integer",
|
||||
ScadaLink.Commons.Types.Enums.DataType.Float => "Float",
|
||||
ScadaLink.Commons.Types.Enums.DataType.Double => "Float",
|
||||
ScadaLink.Commons.Types.Enums.DataType.String => "String",
|
||||
ScadaLink.Commons.Types.Enums.DataType.DateTime => "String",
|
||||
ScadaLink.Commons.Types.Enums.DataType.Binary => "Object",
|
||||
_ => "Object"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user