diff --git a/src/util.spec.ts b/src/util.spec.ts index c3c55cb84..5e4870648 100644 --- a/src/util.spec.ts +++ b/src/util.spec.ts @@ -13,6 +13,7 @@ import { NamespaceType } from './types/NamespaceType'; import { ClassType } from './types/ClassType'; import { ReferenceType } from './types/ReferenceType'; import { SymbolTypeFlag } from './SymbolTable'; +import { createDottedIdentifier, createVariableExpression } from './astUtils/creators'; const sinon = createSandbox(); @@ -44,6 +45,26 @@ describe('util', () => { }); }); + describe('getAllDottedGetPartsAsString', () => { + it('returns undefined when no value found', () => { + expect( + util.getAllDottedGetPartsAsString(undefined) + ).to.eql(undefined); + }); + + it('returns var name', () => { + expect( + util.getAllDottedGetPartsAsString(createVariableExpression('alpha')) + ).to.eql('alpha'); + }); + + it('returns dotted get name', () => { + expect( + util.getAllDottedGetPartsAsString(createDottedIdentifier(['alpha', 'beta'])) + ).to.eql('alpha.beta'); + }); + }); + describe('diagnosticIsSuppressed', () => { it('does not crash when diagnostic is missing location information', () => { const program = new Program({}); diff --git a/src/util.ts b/src/util.ts index bfd5c3074..e6d257900 100644 --- a/src/util.ts +++ b/src/util.ts @@ -22,18 +22,20 @@ import { ObjectType } from './types/ObjectType'; import { StringType } from './types/StringType'; import { VoidType } from './types/VoidType'; import { ParseMode } from './parser/Parser'; -import type { DottedGetExpression, VariableExpression } from './parser/Expression'; +import type { CallExpression, CallfuncExpression, DottedGetExpression, FunctionParameterExpression, IndexedGetExpression, LiteralExpression, NewExpression, TypeExpression, VariableExpression, XmlAttributeGetExpression } from './parser/Expression'; import { Logger, LogLevel } from './Logger'; import type { Identifier, Locatable, Token } from './lexer/Token'; import { TokenKind } from './lexer/TokenKind'; -import { isAssignmentStatement, isBrsFile, isCallExpression, isCallfuncExpression, isDottedGetExpression, isExpression, isFunctionParameterExpression, isGroupingExpression, isIndexedGetExpression, isLiteralExpression, isNewExpression, isTypeExpression, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection'; +import { isAssignmentStatement, isBrsFile, isCallExpression, isCallfuncExpression, isDottedGetExpression, isExpression, isFunctionParameterExpression, isGroupingExpression, isIndexedGetExpression, isLiteralExpression, isTypeExpression, isVariableExpression, isXmlAttributeGetExpression, isXmlFile } from './astUtils/reflection'; import { WalkMode } from './astUtils/visitors'; import { SourceNode } from 'source-map'; import * as requireRelative from 'require-relative'; import type { BrsFile } from './files/BrsFile'; import type { XmlFile } from './files/XmlFile'; -import type { Expression, Statement } from './parser/AstNode'; +import type { AstNode } from './parser/AstNode'; +import { AstNodeKind, type Expression, type Statement } from './parser/AstNode'; import { createIdentifier } from './astUtils/creators'; +import type { AssignmentStatement } from './parser/Statement'; export class Util { public clearConsole() { @@ -1332,7 +1334,52 @@ export class Util { * @param node any ast expression * @returns an array of the parts of the dotted get. If not fully a dotted get, then returns undefined */ - public getAllDottedGetParts(node: Expression | Statement): Identifier[] | undefined { + public getAllDottedGetParts(node: AstNode): Identifier[] | undefined { + //this is a hot function and has been optimized. Don't rewrite unless necessary + const parts: Identifier[] = []; + let nextPart = node; + loop: while (nextPart) { + switch (nextPart?.kind) { + case AstNodeKind.AssignmentStatement: + return [(node as AssignmentStatement).name]; + case AstNodeKind.DottedGetExpression: + parts.push((nextPart as DottedGetExpression)?.name); + nextPart = (nextPart as DottedGetExpression).obj; + continue; + case AstNodeKind.CallExpression: + nextPart = (nextPart as CallExpression).callee; + continue; + case AstNodeKind.TypeExpression: + nextPart = (nextPart as TypeExpression).expression; + continue; + case AstNodeKind.VariableExpression: + parts.push((nextPart as VariableExpression)?.name); + break loop; + case AstNodeKind.LiteralExpression: + parts.push((nextPart as LiteralExpression)?.token as Identifier); + break loop; + case AstNodeKind.IndexedGetExpression: + nextPart = (nextPart as IndexedGetExpression).obj; + continue; + case AstNodeKind.FunctionParameterExpression: + return [(nextPart as FunctionParameterExpression).name]; + case AstNodeKind.GroupingExpression: + parts.push(createIdentifier('()', nextPart.range)); + break loop; + default: + //we found a non-DottedGet expression, so return because this whole operation is invalid. + return undefined; + } + } + return parts.reverse(); + } + + /** + * Gets each part of the dotted get. + * @param node any ast expression + * @returns an array of the parts of the dotted get. If not fully a dotted get, then returns undefined + */ + public getAllDottedGetPartsOld(node: Expression | Statement): Identifier[] | undefined { const parts: Identifier[] = []; let nextPart = node; while (nextPart) { @@ -1366,13 +1413,31 @@ export class Util { return parts.reverse(); } - public getAllDottedGetPartsAsString(node: Expression | Statement, parseMode = ParseMode.BrighterScript) { - const sep = parseMode === ParseMode.BrighterScript ? '.' : '_'; - const hello = this.getAllDottedGetParts(node)?.map(part => part.text).join(sep); - if (!hello) { - console.log(node); + /** + * Given an expression, return all the DottedGet name parts as a string. + * Mostly used to convert namespaced item full names to a strings + */ + public getAllDottedGetPartsAsString(node: Expression | Statement, parseMode = ParseMode.BrighterScript): string { + //this is a hot function and has been optimized. Don't rewrite unless necessary + /* eslint-disable no-var */ + var sep = parseMode === ParseMode.BrighterScript ? '.' : '_'; + const parts = this.getAllDottedGetParts(node) ?? []; + var result = parts[0]?.text; + for (var i = 1; i < parts.length; i++) { + result += sep + parts[i].text; + } + return result; + /* eslint-enable no-var */ + } + + public stringJoin(strings: string[], separator: string) { + // eslint-disable-next-line no-var + var result = strings[0] ?? ''; + // eslint-disable-next-line no-var + for (var i = 1; i < strings.length; i++) { + result += separator + strings[i]; } - return hello; + return result; } /** @@ -1404,35 +1469,37 @@ export class Util { public getDottedGetPath(expression: Expression): [VariableExpression, ...DottedGetExpression[]] { let parts: Expression[] = []; let nextPart = expression; - while (nextPart) { - if (isDottedGetExpression(nextPart)) { - parts.unshift(nextPart); - nextPart = nextPart.obj; - - } else if (isIndexedGetExpression(nextPart) || isXmlAttributeGetExpression(nextPart)) { - nextPart = nextPart.obj; - parts = []; - - } else if (isCallExpression(nextPart) || isCallfuncExpression(nextPart)) { - nextPart = nextPart.callee; - parts = []; - - } else if (isNewExpression(nextPart)) { - nextPart = nextPart.call.callee; - parts = []; - - } else if (isTypeExpression(nextPart)) { - nextPart = nextPart.expression; - - } else if (isVariableExpression(nextPart)) { - parts.unshift(nextPart); - break; - } else { - parts = []; - break; + loop: while (nextPart) { + switch (nextPart?.kind) { + case AstNodeKind.DottedGetExpression: + parts.push(nextPart); + nextPart = (nextPart as DottedGetExpression).obj; + continue; + case AstNodeKind.IndexedGetExpression: + case AstNodeKind.XmlAttributeGetExpression: + nextPart = (nextPart as IndexedGetExpression | XmlAttributeGetExpression).obj; + parts = []; + continue; + case AstNodeKind.CallExpression: + case AstNodeKind.CallfuncExpression: + nextPart = (nextPart as CallExpression | CallfuncExpression).callee; + parts = []; + continue; + case AstNodeKind.NewExpression: + nextPart = (nextPart as NewExpression).call.callee; + parts = []; + continue; + case AstNodeKind.TypeExpression: + nextPart = (nextPart as TypeExpression).expression; + continue; + case AstNodeKind.VariableExpression: + parts.push(nextPart); + break loop; + default: + return [] as any; } } - return parts as any; + return parts.reverse() as any; } /**