diff --git a/src/astUtils/reflection.ts b/src/astUtils/reflection.ts index 8496c1e8f..009f6794e 100644 --- a/src/astUtils/reflection.ts +++ b/src/astUtils/reflection.ts @@ -1,5 +1,5 @@ import type { Body, AssignmentStatement, Block, ExpressionStatement, CommentStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassFieldStatement, ClassMethodStatement, ClassStatement, InterfaceFieldStatement, InterfaceMethodStatement, InterfaceStatement, EnumStatement, EnumMemberStatement, TryCatchStatement, CatchStatement, ThrowStatement, MethodStatement, FieldStatement, ConstStatement, ContinueStatement, DimStatement, TypecastStatement, AliasStatement } from '../parser/Statement'; -import type { LiteralExpression, BinaryExpression, CallExpression, FunctionExpression, NamespacedVariableNameExpression, DottedGetExpression, XmlAttributeGetExpression, IndexedGetExpression, GroupingExpression, EscapedCharCodeLiteralExpression, ArrayLiteralExpression, AALiteralExpression, UnaryExpression, VariableExpression, SourceLiteralExpression, NewExpression, CallfuncExpression, TemplateStringQuasiExpression, TemplateStringExpression, TaggedTemplateStringExpression, AnnotationExpression, FunctionParameterExpression, AAMemberExpression, TypeCastExpression, TernaryExpression, NullCoalescingExpression } from '../parser/Expression'; +import type { LiteralExpression, BinaryExpression, CallExpression, FunctionExpression, NamespacedVariableNameExpression, DottedGetExpression, XmlAttributeGetExpression, IndexedGetExpression, GroupingExpression, EscapedCharCodeLiteralExpression, ArrayLiteralExpression, AALiteralExpression, UnaryExpression, VariableExpression, SourceLiteralExpression, NewExpression, CallfuncExpression, TemplateStringQuasiExpression, TemplateStringExpression, TaggedTemplateStringExpression, AnnotationExpression, FunctionParameterExpression, AAMemberExpression, TypeCastExpression, TernaryExpression, NullCoalescingExpression, RegexLiteralExpression } from '../parser/Expression'; import type { BrsFile } from '../files/BrsFile'; import type { XmlFile } from '../files/XmlFile'; import type { BscFile, File, TypedefProvider } from '../interfaces'; @@ -97,6 +97,9 @@ export function isTernaryExpression(element: AstNode | undefined): element is Te export function isNullCoalescingExpression(element: AstNode | undefined): element is NullCoalescingExpression { return element?.constructor?.name === 'NullCoalescingExpression'; } +export function isRegexLiteralExpression(element: AstNode | undefined): element is RegexLiteralExpression { + return element?.constructor?.name === 'RegexLiteralExpression'; +} export function isEndStatement(element: AstNode | undefined): element is EndStatement { return element?.constructor?.name === 'EndStatement'; } diff --git a/src/bscPlugin/CallExpressionInfo.ts b/src/bscPlugin/CallExpressionInfo.ts index b14ab36d6..4a974e6b4 100644 --- a/src/bscPlugin/CallExpressionInfo.ts +++ b/src/bscPlugin/CallExpressionInfo.ts @@ -25,7 +25,7 @@ export class CallExpressionInfo { expression?: Expression; //the contextually relevant callExpression, which relates to it - callExpression?: CallExpression; + callExpression?: CallExpression | CallfuncExpression; type: CallExpressionType; file: BrsFile; @@ -100,9 +100,9 @@ export class CallExpressionInfo { return util.rangeContains(boundingRange, this.position); } - ascertainCallExpression(): CallExpression { + ascertainCallExpression(): CallExpression | CallfuncExpression { let expression = this.expression; - function isCallFuncOrCallExpression(expression: Expression) { + function isCallFuncOrCallExpression(expression: Expression): expression is CallExpression | CallfuncExpression { return isCallfuncExpression(expression) || isCallExpression(expression); } diff --git a/src/parser/AstNode.spec.ts b/src/parser/AstNode.spec.ts index 9b1ecb13a..4d50917c9 100644 --- a/src/parser/AstNode.spec.ts +++ b/src/parser/AstNode.spec.ts @@ -1,16 +1,19 @@ import { util } from '../util'; import * as fsExtra from 'fs-extra'; import { Program } from '../Program'; -import type { BrsFile } from '../files/BrsFile'; +import { BrsFile } from '../files/BrsFile'; import { expect } from '../chai-config.spec'; -import type { AALiteralExpression, AAMemberExpression, ArrayLiteralExpression, BinaryExpression, CallExpression, CallfuncExpression, DottedGetExpression, FunctionExpression, GroupingExpression, IndexedGetExpression, NewExpression, NullCoalescingExpression, TaggedTemplateStringExpression, TemplateStringExpression, TemplateStringQuasiExpression, TernaryExpression, TypeCastExpression, UnaryExpression, XmlAttributeGetExpression } from './Expression'; +import type { AAMemberExpression } from './Expression'; +import { type AALiteralExpression, type ArrayLiteralExpression, type BinaryExpression, type CallExpression, type CallfuncExpression, type DottedGetExpression, type FunctionExpression, type GroupingExpression, type IndexedGetExpression, type NewExpression, type NullCoalescingExpression, type TaggedTemplateStringExpression, type TemplateStringExpression, type TemplateStringQuasiExpression, type TernaryExpression, type TypeCastExpression, type UnaryExpression, type XmlAttributeGetExpression } from './Expression'; import { expectZeroDiagnostics } from '../testHelpers.spec'; import { tempDir, rootDir, stagingDir } from '../testHelpers.spec'; -import { isAALiteralExpression, isAAMemberExpression, isAnnotationExpression, isArrayLiteralExpression, isAssignmentStatement, isBinaryExpression, isBlock, isCallExpression, isCallfuncExpression, isCatchStatement, isClassStatement, isCommentStatement, isConstStatement, isDimStatement, isDottedGetExpression, isDottedSetStatement, isEnumMemberStatement, isEnumStatement, isExpressionStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isGroupingExpression, isIfStatement, isIncrementStatement, isIndexedGetExpression, isIndexedSetStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInterfaceStatement, isLibraryStatement, isMethodStatement, isNamespaceStatement, isNewExpression, isNullCoalescingExpression, isPrintStatement, isReturnStatement, isTaggedTemplateStringExpression, isTemplateStringExpression, isTemplateStringQuasiExpression, isTernaryExpression, isThrowStatement, isTryCatchStatement, isTypeCastExpression, isUnaryExpression, isWhileStatement, isXmlAttributeGetExpression } from '../astUtils/reflection'; -import type { ClassStatement, FunctionStatement, InterfaceFieldStatement, InterfaceMethodStatement, MethodStatement, InterfaceStatement, CatchStatement, ThrowStatement, EnumStatement, EnumMemberStatement, ConstStatement, Block, CommentStatement, PrintStatement, DimStatement, ForStatement, WhileStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, TryCatchStatement, DottedSetStatement } from './Statement'; +import { isAALiteralExpression, isAAMemberExpression, isAnnotationExpression, isArrayLiteralExpression, isAssignmentStatement, isBinaryExpression, isBlock, isCallExpression, isCallfuncExpression, isCatchStatement, isClassStatement, isCommentStatement, isConstStatement, isDimStatement, isDottedGetExpression, isDottedSetStatement, isEnumMemberStatement, isEnumStatement, isExpressionStatement, isForEachStatement, isForStatement, isFunctionExpression, isFunctionStatement, isGroupingExpression, isIfStatement, isIncrementStatement, isIndexedGetExpression, isIndexedSetStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInterfaceStatement, isLibraryStatement, isLiteralNumber, isMethodStatement, isNamespacedVariableNameExpression, isNamespaceStatement, isNewExpression, isNullCoalescingExpression, isPrintStatement, isRegexLiteralExpression, isReturnStatement, isSourceLiteralExpression, isTaggedTemplateStringExpression, isTemplateStringExpression, isTemplateStringQuasiExpression, isTernaryExpression, isThrowStatement, isTryCatchStatement, isTypeCastExpression, isUnaryExpression, isVariableExpression, isWhileStatement, isXmlAttributeGetExpression } from '../astUtils/reflection'; +import type { Body, ClassStatement, FunctionStatement, InterfaceFieldStatement, InterfaceMethodStatement, MethodStatement, InterfaceStatement, CatchStatement, ThrowStatement, EnumStatement, EnumMemberStatement, ConstStatement, Block, CommentStatement, PrintStatement, DimStatement, ForStatement, WhileStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, TryCatchStatement, DottedSetStatement } from './Statement'; import { AssignmentStatement, EmptyStatement } from './Statement'; import { ParseMode, Parser } from './Parser'; import type { AstNode } from './AstNode'; +import { BrsTranspileState } from './BrsTranspileState'; +import { standardizePath as s } from '../util'; type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; @@ -1670,4 +1673,214 @@ describe('AstNode', () => { testClone(original); }); }); + + describe('Expression chains', () => { + function findNodeByName(ast: Body, name: string) { + return ast.findChild(node => { + if (isVariableExpression(node) && node.name.text === name) { + return true; + } else if (isDottedGetExpression(node) && node.name.text === name) { + return true; + } else if (isSourceLiteralExpression(node) && node.token.text === name) { + return true; + } else if (isXmlAttributeGetExpression(node) && node.name.text === name) { + return true; + } + }); + } + + type FindMatcher = (ast: Body) => AstNode; + function doTest(code: string, startSelector: string | FindMatcher, expectedRootSelector: string | FindMatcher, insertFunctionBody = true) { + const file = new BrsFile(s`${rootDir}/source/main.bs`, 'pkg:/source/main.bs', program); + + file.parse( + !insertFunctionBody ? code : `function test()\n ${code}\nend function` + ); + + expectZeroDiagnostics(file); + + const ast = file.ast; + + const start = typeof startSelector === 'function' ? startSelector(ast) : findNodeByName(ast, startSelector as string); + const expectedRoot = typeof expectedRootSelector === 'function' ? expectedRootSelector(ast) : findNodeByName(ast, expectedRootSelector as string); + + expect(start).not.to.be.undefined; + expect(expectedRoot).not.to.be.undefined; + + const root = start.getExpressionChainRoot(); + + //transpile both items so we can compare the things they actually represent. (helps sanity check) + expect( + root.transpile(new BrsTranspileState(file)).toString() + ).to.eql( + expectedRoot.transpile(new BrsTranspileState(file)).toString() + ); + + //the real test. did we get the exact same instance of the root that we were expecting? + expect(root).to.equal(expectedRoot); + } + + it('finds root of dotted gets', () => { + doTest(`func = alpha.beta.charlie.delta`, 'alpha', 'delta'); + doTest(`func = alpha.beta.charlie.delta`, 'beta', 'delta'); + doTest(`func = alpha.beta.charlie.delta`, 'charlie', 'delta'); + doTest(`func = alpha.beta.charlie.delta`, 'delta', 'delta'); + }); + + it('finds root of dotted get inside indexed get', () => { + doTest(`func = alpha.beta[charlie.delta]`, 'alpha', ast => ast.findChild(isIndexedGetExpression)); + doTest(`func = alpha.beta[charlie.delta]`, 'beta', ast => ast.findChild(isIndexedGetExpression)); + doTest(`func = alpha.beta[charlie.delta]`, 'charlie', 'delta'); + doTest(`func = alpha.beta[charlie.delta]`, 'delta', 'delta'); + }); + + it('finds root of variable expression', () => { + doTest(`func = alpha`, 'alpha', 'alpha'); + }); + + it('finds root of function call', () => { + doTest(`func = alpha()`, 'alpha', ast => ast.findChild(isCallExpression)); + doTest(`func = alpha.beta()`, 'alpha', ast => ast.findChild(isCallExpression)); + }); + + it('finds root of call inside call', () => { + doTest(`func = alpha()`, 'alpha', ast => ast.findChild(isCallExpression)); + doTest(`func = alpha(beta())`, 'beta', ast => { + return ast.findChild(x => isVariableExpression(x) && x.name.text === 'beta').parent; + }); + doTest(`func = alpha(beta())`, 'alpha', ast => { + return ast.findChild(x => isVariableExpression(x) && x.name.text === 'alpha').parent; + }); + }); + + it('finds root of literal number inside call', () => { + doTest(`func = alpha(1)`, ast => ast.findChild(isLiteralNumber), ast => ast.findChild(isLiteralNumber)); + }); + + it('finds wrapped of literal number inside call', () => { + doTest(`func = alpha(1)`, ast => ast.findChild(isLiteralNumber), ast => ast.findChild(isLiteralNumber)); + }); + + it('GroupExpressions cause new expression chains', () => { + doTest(`func = (((one).two).three)`, 'one', 'one'); + doTest(`func = (((one).two).three)`, ast => { + //the grouping expression around `one` + return ast.findChild(x => isVariableExpression(x) && x.name.text === 'one').parent; + }, 'two'); + doTest(`func = (((one).two).three)`, ast => { + //the grouping expression around `(one).two` + return ast.findChild(x => isDottedGetExpression(x) && x.name.text === 'two').parent; + }, 'three'); + doTest(`func = (((one).two).three)`, ast => { + //the grouping expression around `((one).two).three` + return ast.findChild(x => isDottedGetExpression(x) && x.name.text === 'three').parent; + }, ast => { + //the grouping expression around `((one).two).three` + return ast.findChild(x => isDottedGetExpression(x) && x.name.text === 'three').parent; + }); + }); + + it('UnaryExpression are not included in expressionChainRoot', () => { + doTest(`isAlive = [not isDead]`, 'isDead', 'isDead'); + }); + + it('SourceLiteralExpression do not cause issues', () => { + doTest(`isAlive = [LINE_NUM.ToStr()]`, 'LINE_NUM', ast => ast.findChild(isCallExpression)); + }); + + it('SourceLiteralExpression do not cause issues', () => { + doTest(`func = [function(p1 = alpha.beta)\nend function]`, 'alpha', 'beta'); + }); + + it('BinaryExpression knows its a root', () => { + doTest(`func = [1 + "two"]`, ast => ast.findChild(isBinaryExpression), ast => ast.findChild(isBinaryExpression)); + }); + + it('FunctionExpression knows its a root', () => { + doTest(`func = [sub() : end sub]`, ast => ast.findChild(isFunctionExpression), ast => ast.findChild(isFunctionExpression)); + }); + + it('NamespacedVariableExpression knows its a root', () => { + doTest(` + namespace alpha.beta + class charlie extends delta.echo + end class + end namespace + `, ast => ast.findChild(isNamespacedVariableNameExpression), ast => ast.findChild(isNamespacedVariableNameExpression), false); + }); + + it('XmlAttributeGetExpression works', () => { + doTest(`result = [thing@name]`, 'thing', 'name'); + }); + + it('LiteralExpression knows its a root when standalone', () => { + doTest(`result = [1]`, ast => ast.findChild(isLiteralNumber), ast => ast.findChild(isLiteralNumber)); + }); + + //skipped because this is a bug in the parser (it's valid syntax on device) + it.skip('LiteralExpression is not a root when used on LHS of dotted get', () => { + doTest(`result = 1.ToStr()`, ast => ast.findChild(isLiteralNumber), ast => ast.findChild(isCallExpression)); + }); + + it('ArrayLiteralExpression knows its a root', () => { + doTest(`result = [1,2,3]`, ast => ast.findChild(isArrayLiteralExpression), ast => ast.findChild(isArrayLiteralExpression)); + }); + + it('AALiteralExpression knows its a root', () => { + doTest(`result = [{}]`, ast => ast.findChild(isAALiteralExpression), ast => ast.findChild(isAALiteralExpression)); + }); + + it('AAMemberExpression knows its a root', () => { + doTest(`result = [{ one: 1}]`, ast => ast.findChild(isAAMemberExpression), ast => ast.findChild(isAAMemberExpression)); + }); + + it('VariableExpression knows its a root', () => { + doTest(`result = [one]`, 'one', 'one'); + }); + + it('NewExpression knows its a root', () => { + doTest(`result = [new Movie()]`, ast => ast.findChild(isNewExpression), ast => ast.findChild(isNewExpression)); + }); + + it('CallfuncExpression properly passes along', () => { + doTest(`result = [ node@.someCallfunc() ]`, 'node', ast => ast.findChild(isCallfuncExpression)); + }); + + it('TemplateStringExpression knows its a root', () => { + doTest('result = [ `some text` ]', ast => ast.findChild(isTemplateStringExpression), ast => ast.findChild(isTemplateStringExpression)); + }); + + it('TaggedStringExpression knows its a root', () => { + doTest('result = [ someTag`some text` ]', ast => ast.findChild(isTaggedTemplateStringExpression), ast => ast.findChild(isTaggedTemplateStringExpression)); + }); + + it('AnnotationExpression knows its a root', () => { + doTest(` + @SomeAnnotation() + sub SomeFunc() + end sub + `, ast => ast.findChild(x => { + return isFunctionStatement(x); + }).annotations[0], ast => ast.findChild(x => { + return isFunctionStatement(x); + }).annotations[0], false); + }); + + it('TernaryExpression knows its a root', () => { + doTest('result = [ true ? 1 : 2 ]', ast => ast.findChild(isTernaryExpression), ast => ast.findChild(isTernaryExpression)); + }); + + it('NullCoalescingExpression knows its a root', () => { + doTest('result = [ 1 ?? 2 ]', ast => ast.findChild(isNullCoalescingExpression), ast => ast.findChild(isNullCoalescingExpression)); + }); + + it('regex works correctly', () => { + doTest('result = [ /one/ ]', ast => ast.findChild(isRegexLiteralExpression), ast => ast.findChild(isRegexLiteralExpression)); + doTest('result = [ /one/.exec() ]', ast => ast.findChild(isRegexLiteralExpression), ast => ast.findChild(isCallExpression)); + }); + + it('NullCoalescingExpression knows its a root', () => { + doTest('result = [1 as string]', ast => ast.findChild(isTypeCastExpression), ast => ast.findChild(isTypeCastExpression)); + }); + }); }); diff --git a/src/parser/AstNode.ts b/src/parser/AstNode.ts index 17c63731e..456f4cc1d 100644 --- a/src/parser/AstNode.ts +++ b/src/parser/AstNode.ts @@ -8,6 +8,7 @@ import type { BrsTranspileState } from './BrsTranspileState'; import type { TranspileResult } from '../interfaces'; import type { AnnotationExpression } from './Expression'; import util from '../util'; +import { isExpression, isStatement } from '../astUtils/reflection'; /** * A BrightScript AST node @@ -158,6 +159,55 @@ export abstract class AstNode { return clone; } + + /** + * Get the root of this expression chain. + * For example, `alpha.beta(charlie.delta)`, the roots would be the DottedGetExpression for `delta`, and the `CallExpression for `beta(...)`. + * @deprecated this is a preview feature and may be removed or changed in the future + */ + public getExpressionChainRoot(): Expression | undefined { + let node: Expression = this; + + while (node) { + //if the node is a root, return it + if (node.isExpressionChainRoot) { + return node; + //if we have a parent, do another iteration + } else if (isExpression(node.parent)) { + node = node.parent; + } else { + //there's no parent, this node must be the root + return node; + } + } + return undefined; + } + + /** + * Is this node the root of an expression chain? + * @deprecated this is a preview feature and may be removed or changed in the future + */ + public get isExpressionChainRoot() { + //if any of these conditions are true, then this node is an expression chain root + if ( + //if there is no parent, + !this.parent || + //our parent is a `Statement` + isStatement(this.parent) || + //is NOT a part of our parents expression chain + this.parent.childIsInExpressionChain?.(this) === false + ) { + return true; + } + return false; + } + + /** + * Is the node a direct child in the expression chain of this node? + * @param child the child node to check + * @deprecated this is a preview feature and may be removed or changed in the future + */ + protected abstract childIsInExpressionChain(child: AstNode): boolean; } export abstract class Statement extends AstNode { @@ -169,6 +219,11 @@ export abstract class Statement extends AstNode { * Annotations for this statement */ public annotations: AnnotationExpression[] | undefined; + + protected childIsInExpressionChain(child: AstNode): boolean { + // statements cannot contribute child nodes to the same expression chain + return false; + } } diff --git a/src/parser/Expression.ts b/src/parser/Expression.ts index 4388989cf..c4dead2f5 100644 --- a/src/parser/Expression.ts +++ b/src/parser/Expression.ts @@ -62,6 +62,11 @@ export class BinaryExpression extends Expression { ['left', 'right'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + //we can't be part of expression chains. we're our own chain. + return false; + } } export class CallExpression extends Expression { @@ -144,6 +149,11 @@ export class CallExpression extends Expression { ['callee', 'args'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + //only the callee can be part of our expression chain + return child === this.callee; + } } export class FunctionExpression extends Expression implements TypedefProvider { @@ -407,6 +417,10 @@ export class FunctionExpression extends Expression implements TypedefProvider { }, { walkMode: WalkMode.visitExpressions }); return clone; } + + protected childIsInExpressionChain(child: AstNode): boolean { + return false; //these can't be part of an expression chain + } } export class FunctionParameterExpression extends Expression { @@ -492,6 +506,11 @@ export class FunctionParameterExpression extends Expression { ['defaultValue'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class NamespacedVariableNameExpression extends Expression { @@ -549,6 +568,11 @@ export class NamespacedVariableNameExpression extends Expression { ) ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class DottedGetExpression extends Expression { @@ -563,7 +587,6 @@ export class DottedGetExpression extends Expression { super(); this.range = util.createBoundingRange(this.obj, this.dot, this.name); } - public readonly range: Range | undefined; transpile(state: BrsTranspileState) { @@ -604,6 +627,12 @@ export class DottedGetExpression extends Expression { ['obj'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + //the `obj` is the only child node that can be part of our expression chain + return this.obj === child; + } + } export class XmlAttributeGetExpression extends Expression { @@ -645,6 +674,11 @@ export class XmlAttributeGetExpression extends Expression { ['obj'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + //the `obj` is the only child node that can be part of our expression chain + return this.obj === child; + } } export class IndexedGetExpression extends Expression { @@ -714,6 +748,10 @@ export class IndexedGetExpression extends Expression { ['obj', 'index', 'additionalIndexes'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + return this.obj === child; + } } export class GroupingExpression extends Expression { @@ -759,6 +797,11 @@ export class GroupingExpression extends Expression { ['expression'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class LiteralExpression extends Expression { @@ -810,6 +853,11 @@ export class LiteralExpression extends Expression { ) ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } /** @@ -842,6 +890,11 @@ export class EscapedCharCodeLiteralExpression extends Expression { ) ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class ArrayLiteralExpression extends Expression { @@ -922,6 +975,11 @@ export class ArrayLiteralExpression extends Expression { ['elements'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class AAMemberExpression extends Expression { @@ -958,6 +1016,10 @@ export class AAMemberExpression extends Expression { ); } + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class AALiteralExpression extends Expression { @@ -1058,6 +1120,11 @@ export class AALiteralExpression extends Expression { ['elements'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class UnaryExpression extends Expression { @@ -1101,6 +1168,11 @@ export class UnaryExpression extends Expression { ['right'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class VariableExpression extends Expression { @@ -1156,6 +1228,11 @@ export class VariableExpression extends Expression { ) ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class SourceLiteralExpression extends Expression { @@ -1269,6 +1346,11 @@ export class SourceLiteralExpression extends Expression { ) ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } /** @@ -1322,6 +1404,11 @@ export class NewExpression extends Expression { ['call'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class CallfuncExpression extends Expression { @@ -1404,6 +1491,11 @@ export class CallfuncExpression extends Expression { ['callee', 'args'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + //only the callee can be part of our expression chain + return child === this.callee; + } } /** @@ -1453,6 +1545,11 @@ export class TemplateStringQuasiExpression extends Expression { ['expressions'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class TemplateStringExpression extends Expression { @@ -1552,6 +1649,11 @@ export class TemplateStringExpression extends Expression { ['quasis', 'expressions'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class TaggedTemplateStringExpression extends Expression { @@ -1642,6 +1744,11 @@ export class TaggedTemplateStringExpression extends Expression { ['quasis', 'expressions'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class AnnotationExpression extends Expression { @@ -1699,6 +1806,11 @@ export class AnnotationExpression extends Expression { ); return clone; } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class TernaryExpression extends Expression { @@ -1806,6 +1918,11 @@ export class TernaryExpression extends Expression { ['test', 'consequent', 'alternate'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class NullCoalescingExpression extends Expression { @@ -1905,6 +2022,11 @@ export class NullCoalescingExpression extends Expression { ['consequent', 'alternate'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } export class RegexLiteralExpression extends Expression { @@ -1956,8 +2078,12 @@ export class RegexLiteralExpression extends Expression { }) ); } -} + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } +} export class TypeCastExpression extends Expression { constructor( @@ -1994,6 +2120,11 @@ export class TypeCastExpression extends Expression { ['obj'] ); } + + protected childIsInExpressionChain(child: AstNode): boolean { + // this node cannot contribute child nodes to the same expression chain + return false; + } } // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style