diff --git a/src/bscPlugin/validation/ScopeValidator.spec.ts b/src/bscPlugin/validation/ScopeValidator.spec.ts index 8c77e25b3..852479dd2 100644 --- a/src/bscPlugin/validation/ScopeValidator.spec.ts +++ b/src/bscPlugin/validation/ScopeValidator.spec.ts @@ -1836,6 +1836,43 @@ describe('ScopeValidator', () => { expectZeroDiagnostics(program); }); + it('does not have diagnostic when accessing unknown member of node in Brightscript mode', () => { + program.setFile('source/main.brs', ` + ' @param {roSGNode} node + function testNodeMember(node) + x = node.whatever + return x + end function + `); + program.validate(); + expectZeroDiagnostics(program); + }); + + it('does not have diagnostic when accessing unknown member of contentnode in Brightscript mode', () => { + program.setFile('source/main.brs', ` + ' @param {roSgNodeCOntentNode} node + function testNodeMember(node) + x = node.whatever + return x + end function + `); + program.validate(); + expectZeroDiagnostics(program); + }); + + it('does not have diagnostic when accessing unknown member of created node in Brightscript mode', () => { + program.setFile('source/main.brs', ` + ' @param {string} nodeSubtype + function testNodeMember(nodeSubtype) + x = createObject("roSgNode",nodeSubtype) + x.whatever = true + return x + end function + `); + program.validate(); + expectZeroDiagnostics(program); + }); + it('allows anything on m in an anonymous function', () => { program.setFile('source/main.bs', ` function test() diff --git a/src/bscPlugin/validation/ScopeValidator.ts b/src/bscPlugin/validation/ScopeValidator.ts index 462f0b8a8..59940a741 100644 --- a/src/bscPlugin/validation/ScopeValidator.ts +++ b/src/bscPlugin/validation/ScopeValidator.ts @@ -1239,6 +1239,8 @@ export class ScopeValidator { * In particular, since BrightScript does not support Unions, and there's no way to cast them to something else * if the result of .getType() is a union, and we're in a .brs (brightScript) file, treat the result as Dynamic * + * Also, for BrightScript parse-mode, if .getType() returns a node type, do not validate unknown members. + * * In most cases, this returns the result of node.getType() * * @param file the current file being processed @@ -1265,6 +1267,11 @@ export class ScopeValidator { //this is a union return DynamicType.instance; } + + if (isComponentType(type)) { + // modify type to allow any member access for Node types + type.changeUnknownMemberToDynamic = true; + } } // by default return the result of node.getType() diff --git a/src/types/InheritableType.ts b/src/types/InheritableType.ts index 85de856b1..1271c87a1 100644 --- a/src/types/InheritableType.ts +++ b/src/types/InheritableType.ts @@ -14,6 +14,8 @@ export abstract class InheritableType extends BscType { } } + changeUnknownMemberToDynamic = false; + getMemberType(memberName: string, options: GetTypeOptions) { let hasRoAssociativeArrayAsAncestor = this.name.toLowerCase() === 'roassociativearray' || this.getAncestorTypeList()?.find(ancestorType => ancestorType.name.toLowerCase() === 'roassociativearray'); @@ -21,7 +23,12 @@ export abstract class InheritableType extends BscType { return super.getMemberType(memberName, options) ?? DynamicType.instance; } - return super.getMemberType(memberName, { ...options, fullName: memberName, tableProvider: () => this.memberTable }); + const resultType = super.getMemberType(memberName, { ...options, fullName: memberName, tableProvider: () => this.memberTable }); + + if (this.changeUnknownMemberToDynamic && !resultType.isResolvable()) { + return DynamicType.instance; + } + return resultType; } public toString() {