diff --git a/src/files/BrsFile.Class.spec.ts b/src/files/BrsFile.Class.spec.ts index 608caaea7..9ed28f883 100644 --- a/src/files/BrsFile.Class.spec.ts +++ b/src/files/BrsFile.Class.spec.ts @@ -83,6 +83,30 @@ describe('BrsFile BrighterScript classes', () => { expect(duckClass.memberMap['move']).to.exist; }); + it('supports various namespace configurations', async () => { + await program.addOrReplaceFile({ src: `${rootDir}/source/main.bs`, dest: 'source/main.bs' }, ` + class Animal + sub new() + bigBird = new Birds.Bird() + donald = new Birds.Duck() + end sub + end class + + namespace Birds + class Bird + sub new() + dog = new Animal() + donald = new Duck() + end sub + end class + class Duck + end class + end namespace + `); + await program.validate(); + expect(program.getDiagnostics()[0]?.message).not.to.exist; + }); + describe('transpile', () => { it('follows correct sequence for property initializers', async () => { await testTranspile(` @@ -305,6 +329,44 @@ describe('BrsFile BrighterScript classes', () => { `, undefined, 'source/main.bs'); }); + it('properly transpiles classes from outside current namespace', async () => { + await addFile('source/Animals.bs', ` + namespace Animals + class Duck + end class + end namespace + class Bird + end class + `); + await testTranspile(` + namespace Animals + sub init() + donaldDuck = new Duck() + daffyDuck = new Animals.Duck() + bigBird = new Bird() + end sub + end namespace + `, ` + sub Animals_init() + donaldDuck = Animals_Duck() + daffyDuck = Animals_Duck() + bigBird = Bird() + end sub + `, undefined, 'source/main.bs'); + }); + + it('properly transpiles new statement for missing class ', async () => { + await testTranspile(` + sub main() + bob = new Human() + end sub + `, ` + sub main() + bob = Human() + end sub + `, undefined, 'source/main.bs'); + }); + it('new keyword transpiles correctly', async () => { await addFile('source/Animal.bs', ` class Animal diff --git a/src/parser/Expression.ts b/src/parser/Expression.ts index 11954ae5b..417872dd8 100644 --- a/src/parser/Expression.ts +++ b/src/parser/Expression.ts @@ -67,6 +67,9 @@ export class CallExpression extends Expression { readonly openingParen: Token, readonly closingParen: Token, readonly args: Expression[], + /** + * The namespace that currently wraps this call expression. This is NOT the namespace of the callee...that will be represented in the callee expression itself. + */ readonly namespaceName: NamespacedVariableNameExpression ) { super(); @@ -75,11 +78,15 @@ export class CallExpression extends Expression { public readonly range: Range; - transpile(state: TranspileState) { + transpile(state: TranspileState, nameOverride?: string) { let result = []; //transpile the name - result.push(...this.callee.transpile(state)); + if (nameOverride) { + result.push(state.sourceNode(this.callee, nameOverride)); + } else { + result.push(...this.callee.transpile(state)); + } result.push( new SourceNode(this.openingParen.range.start.line + 1, this.openingParen.range.start.character, state.pathAbsolute, '(') @@ -845,7 +852,13 @@ export class NewExpression extends Expression { public readonly range: Range; public transpile(state: TranspileState) { - return this.call.transpile(state); + const cls = state.file.getClassByName( + this.className.getName(ParseMode.BrighterScript), + this.namespaceName?.getName(ParseMode.BrighterScript) + ); + //new statements within a namespace block can omit the leading namespace if the class resides in that same namespace. + //So we need to figure out if this is a namespace-omitted class, or if this class exists without a namespace. + return this.call.transpile(state, cls?.getName(ParseMode.BrightScript)); } walk(visitor: WalkVisitor, options: WalkOptions) { diff --git a/src/util.ts b/src/util.ts index 0a7c3c4f8..6cd26a375 100644 --- a/src/util.ts +++ b/src/util.ts @@ -934,8 +934,8 @@ export class Util { /** * Given the class name text, return a namespace-prefixed name. * If the name already has a period in it, or the namespaceName was not provided, return the class name as is. - * If the name does not have a period, and a namespaceName was provided, return the class name prepended - * by the namespace name + * If the name does not have a period, and a namespaceName was provided, return the class name prepended by the namespace name. + * If no namespace is provided, return the `className` unchanged. */ public getFulllyQualifiedClassName(className: string, namespaceName?: string) { if (className.includes('.') === false && namespaceName) { diff --git a/src/validators/ClassValidator.ts b/src/validators/ClassValidator.ts index d7ef567a4..02af9de66 100644 --- a/src/validators/ClassValidator.ts +++ b/src/validators/ClassValidator.ts @@ -35,7 +35,12 @@ export class BsClassValidator { */ private getClassByName(className: string, namespaceName?: string) { let fullName = util.getFulllyQualifiedClassName(className, namespaceName); - return this.classes[fullName.toLowerCase()]; + let cls = this.classes[fullName.toLowerCase()]; + //if we couldn't find the class by its full namespaced name, look for a global class with that name + if (!cls) { + cls = this.classes[className.toLowerCase()]; + } + return cls; } /**