diff --git a/src/Scope.ts b/src/Scope.ts index 925464bc3..f9fb878ca 100644 --- a/src/Scope.ts +++ b/src/Scope.ts @@ -17,7 +17,7 @@ import { URI } from 'vscode-uri'; import { LogLevel } from './Logger'; import type { BrsFile } from './files/BrsFile'; import type { DependencyGraph, DependencyChangedEvent } from './DependencyGraph'; -import { isBrsFile, isMethodStatement, isClassStatement, isConstStatement, isCustomType, isEnumStatement, isFunctionStatement, isFunctionType, isXmlFile, isNamespaceStatement } from './astUtils/reflection'; +import { isBrsFile, isMethodStatement, isClassStatement, isConstStatement, isCustomType, isEnumStatement, isFunctionStatement, isFunctionType, isXmlFile, isNamespaceStatement, isEnumMemberStatement } from './astUtils/reflection'; import { SymbolTable } from './SymbolTable'; import type { Statement } from './parser/AstNode'; @@ -141,6 +141,31 @@ export class Scope { return enumeration; } + /** + * Get an Enum and its containing file by the Enum name + * @param enumMemberName - The Enum name, including the namespace of the enum if possible + * @param containingNamespace - The namespace used to resolve relative enum names. (i.e. the namespace around the current statement trying to find a enum) + */ + public getEnumMemberFileLink(enumMemberName: string, containingNamespace?: string): FileLink { + let lowerNameParts = enumMemberName?.split('.'); + let memberName = lowerNameParts.splice(lowerNameParts.length - 1, 1)?.[0]; + let lowerName = lowerNameParts.join('.').toLowerCase(); + const enumMap = this.getEnumMap(); + + let enumeration = enumMap.get( + util.getFullyQualifiedClassName(lowerName, containingNamespace?.toLowerCase()) + ); + //if we couldn't find the enum by its full namespaced name, look for a global enum with that name + if (!enumeration) { + enumeration = enumMap.get(lowerName); + } + if (enumeration) { + let member = enumeration.item.findChild((child) => isEnumMemberStatement(child) && child.name === memberName); + return member ? { item: member, file: enumeration.file } : undefined; + } + return enumeration; + } + /** * Get a constant and its containing file by the constant name * @param constName - The constant name, including the namespace of the constant if possible diff --git a/src/files/BrsFile.spec.ts b/src/files/BrsFile.spec.ts index a8ac24165..1b75588a9 100644 --- a/src/files/BrsFile.spec.ts +++ b/src/files/BrsFile.spec.ts @@ -3389,5 +3389,41 @@ describe('BrsFile', () => { range: util.createRange(5, 26, 5, 33) }]); }); + it('returns enum locations', () => { + const file = program.setFile('source/main.bs', ` + sub main() + print alpha.beta.people.charlie + end sub + namespace alpha.beta + enum people + charlie = "charles" + end enum + end namespace + `); + program.validate(); + //print alpha.beta.char|lie + expect(program.getDefinition(file.srcPath, Position.create(2, 40))).to.eql([{ + uri: URI.file(file.srcPath).toString(), + range: util.createRange(5, 25, 5, 31) + }]); + }); + it('returns enum member locations', () => { + const file = program.setFile('source/main.bs', ` + sub main() + print alpha.beta.people.charlie + end sub + namespace alpha.beta + enum people + charlie = "charles" + end enum + end namespace + `); + program.validate(); + //print alpha.beta.char|lie + expect(program.getDefinition(file.srcPath, Position.create(2, 48))).to.eql([{ + uri: URI.file(file.srcPath).toString(), + range: util.createRange(6, 24, 6, 31) + }]); + }); }); }); diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts index 041574d98..0a26ba0d5 100644 --- a/src/files/BrsFile.ts +++ b/src/files/BrsFile.ts @@ -1436,7 +1436,7 @@ export class BrsFile { const expression = this.getClosestExpression(position); if (expression) { - let containingNamespace = this.getNamespaceStatementForPosition(expression.range.start)?.getName(ParseMode.BrighterScript); + let containingNamespace = expression.findAncestor(isNamespaceStatement)?.getName(ParseMode.BrighterScript); const fullName = util.getAllDottedGetParts(expression)?.map(x => x.text).join('.'); //find a constant with this name @@ -1450,6 +1450,29 @@ export class BrsFile { ); return results; } + if (isDottedGetExpression(expression)) { + + const enumLink = scope.getEnumFileLink(fullName, containingNamespace); + if (enumLink) { + results.push( + util.createLocation( + URI.file(enumLink.file.srcPath).toString(), + enumLink.item.tokens.name.range + ) + ); + return results; + } + const enumMemberLink = scope.getEnumMemberFileLink(fullName, containingNamespace); + if (enumMemberLink) { + results.push( + util.createLocation( + URI.file(enumMemberLink.file.srcPath).toString(), + enumMemberLink.item.tokens.name.range + ) + ); + return results; + } + } } let textToSearchFor = token.text.toLowerCase();