From a6be2996b03be1c35ad0b96c56f9be072917b6b9 Mon Sep 17 00:00:00 2001 From: Mark Pearce Date: Mon, 23 Feb 2026 09:51:28 -0400 Subject: [PATCH 1/2] First steps toward trying to support custom types in xml --- src/XmlScope.spec.ts | 77 +++++++++++++++++++++- src/XmlScope.ts | 21 +++++- src/bscPlugin/validation/ScopeValidator.ts | 14 ++-- src/files/XmlFile.ts | 4 +- src/parser/SGTypes.ts | 3 + src/util.ts | 6 +- 6 files changed, 116 insertions(+), 9 deletions(-) diff --git a/src/XmlScope.spec.ts b/src/XmlScope.spec.ts index 1517494f1..5d4de7505 100644 --- a/src/XmlScope.spec.ts +++ b/src/XmlScope.spec.ts @@ -10,7 +10,7 @@ import { createSandbox } from 'sinon'; import { ComponentType } from './types/ComponentType'; import { SymbolTypeFlag } from './SymbolTypeFlag'; import { AssociativeArrayType } from './types/AssociativeArrayType'; -import { ArrayType, BooleanType, DoubleType, DynamicType, FloatType, IntegerType, StringType, TypedFunctionType, UnionType } from './types'; +import { ArrayType, BooleanType, DoubleType, DynamicType, FloatType, IntegerType, InterfaceType, StringType, TypedFunctionType, UnionType } from './types'; const sinon = createSandbox(); describe('XmlScope', () => { @@ -315,5 +315,80 @@ describe('XmlScope', () => { expectZeroDiagnostics(program); }); + describe.only('custom types', () => { + it('allows built-in node types as field types', () => { + program.setFile('components/Widget.xml', trim` + + + + + + + `); + program.validate(); + expectZeroDiagnostics(program); + const widgetTypeResult = program.globalScope.symbolTable.getSymbolType('roSGNodeWidget', { flags: SymbolTypeFlag.typetime }); + expectTypeToBe(widgetTypeResult, ComponentType); + const widgetType = widgetTypeResult as ComponentType; + const labelNodeType = widgetType.getMemberType('labelNode', { flags: SymbolTypeFlag.runtime }); + expectTypeToBe(labelNodeType, ComponentType); + expectTypeToBe(labelNodeType.getMemberType('text', { flags: SymbolTypeFlag.runtime }), StringType); + }); + + it('allows unions of primitive types as field types', () => { + program.setFile('components/Widget.xml', trim` + + + + + + + `); + program.validate(); + expectZeroDiagnostics(program); + const widgetTypeResult = program.globalScope.symbolTable.getSymbolType('roSGNodeWidget', { flags: SymbolTypeFlag.typetime }); + expectTypeToBe(widgetTypeResult, ComponentType); + const widgetType = widgetTypeResult as ComponentType; + const publicIdType = widgetType.getMemberType('publicId', { flags: SymbolTypeFlag.runtime }) as UnionType; + expectTypeToBe(publicIdType, UnionType); + expect(publicIdType.types).to.include(IntegerType.instance); + expect(publicIdType.types).to.include(StringType.instance); + }); + + it('disallows unknown types', () => { + program.setFile('components/Widget.xml', trim` + + + + + + + `); + program.validate(); + expectDiagnostics(program, [{ + ...DiagnosticMessages.xmlInvalidFieldType('UnknownType'), + location: { range: Range.create(3, 36, 3, 47) } + }]); + }); + + it('allows types defined in bs files in the scope', () => { + program.setFile('components/Widget.xml', trim` + + +