Skip to content

Commit

Permalink
Fix typescript error for ast parent setting (#659)
Browse files Browse the repository at this point in the history
  • Loading branch information
TwitchBronBron committed Aug 4, 2022
1 parent 60cf8bf commit 3f253e5
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 41 deletions.
57 changes: 57 additions & 0 deletions src/bscPlugin/validation/BrsFileValidator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { expect } from 'chai';
import type { BrsFile } from '../../files/BrsFile';
import type { AALiteralExpression, DottedGetExpression } from '../../parser/Expression';
import type { ClassStatement, FunctionStatement, NamespaceStatement, PrintStatement } from '../../parser/Statement';
import { Program } from '../../Program';

describe('BrsFileValidator', () => {
let program: Program;
beforeEach(() => {
program = new Program({});
});

it('links dotted get expression parents', () => {
const file = program.setFile<BrsFile>('source/main.bs', `
sub main()
print {}.beta.charlie
end sub
`);
program.validate();
const func = (file.parser.ast.statements[0] as FunctionStatement);
const print = func.func.body.statements[0] as PrintStatement;
expect(print.parent).to.equal(func.func.body);

const charlie = print.expressions[0] as DottedGetExpression;
expect(charlie.parent).to.equal(print);

const beta = charlie.obj as DottedGetExpression;
expect(beta.parent).to.equal(charlie);

const aaLiteral = beta.obj as AALiteralExpression;
expect(aaLiteral.parent).to.equal(beta);
});

it('links NamespacedVariableNameExpression dotted get parents', () => {
const file = program.setFile<BrsFile>('source/main.bs', `
namespace alpha.bravo
class Delta extends alpha.bravo.Charlie
end class
class Charlie
end class
end namespace
`);
program.validate();
const namespace = (file.parser.ast.statements[0] as NamespaceStatement);
const deltaClass = namespace.body.statements[0] as ClassStatement;
expect(deltaClass.parent).to.equal(namespace.body);

const charlie = (deltaClass.parentClassName.expression as DottedGetExpression);
expect(charlie.parent).to.equal(deltaClass.parentClassName);

const bravo = charlie.obj as DottedGetExpression;
expect(bravo.parent).to.equal(charlie);

const alpha = bravo.obj as DottedGetExpression;
expect(alpha.parent).to.equal(bravo);
});
});
55 changes: 39 additions & 16 deletions src/bscPlugin/validation/BrsFileValidator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { isClassStatement, isCommentStatement, isConstStatement, isEnumStatement, isFunctionStatement, isImportStatement, isInterfaceStatement, isLibraryStatement, isLiteralExpression, isNamespaceStatement } from '../../astUtils/reflection';
import { isClassStatement, isCommentStatement, isConstStatement, isDottedGetExpression, isEnumStatement, isFunctionStatement, isImportStatement, isInterfaceStatement, isLibraryStatement, isLiteralExpression, isNamespacedVariableNameExpression, isNamespaceStatement } from '../../astUtils/reflection';
import { WalkMode } from '../../astUtils/visitors';
import { DiagnosticMessages } from '../../DiagnosticMessages';
import type { BrsFile } from '../../files/BrsFile';
import type { OnFileValidateEvent } from '../../interfaces';
import type { AstNode, OnFileValidateEvent } from '../../interfaces';
import { TokenKind } from '../../lexer/TokenKind';
import type { LiteralExpression } from '../../parser/Expression';
import type { EnumMemberStatement, EnumStatement } from '../../parser/Statement';
Expand All @@ -18,30 +18,53 @@ export class BrsFileValidator {
this.flagTopLevelStatements();
}

/**
* Set the parent node on a given AstNode. This handles some edge cases where not every expression is iterated normally,
* so it will also reach into nested objects to set their parent values as well
*/
private setParent(node1: AstNode, parent: AstNode) {
const pairs = [[node1, parent]];
while (pairs.length > 0) {
const [childNode, parentNode] = pairs.pop();
//skip this entry if there's already a parent
if (childNode?.parent) {
continue;
}
if (isDottedGetExpression(childNode)) {
if (!childNode.obj.parent) {
pairs.push([childNode.obj, childNode]);
}
} else if (isNamespaceStatement(childNode)) {
//namespace names shouldn't be walked, but it needs its parent assigned
pairs.push([childNode.nameExpression, childNode]);
} else if (isClassStatement(childNode)) {
//class extends names don't get walked, but it needs its parent
if (childNode.parentClassName) {
pairs.push([childNode.parentClassName, childNode]);
}
} else if (isInterfaceStatement(childNode)) {
//class extends names don't get walked, but it needs its parent
if (childNode.parentInterfaceName) {
pairs.push([childNode.parentInterfaceName, childNode]);
}
} else if (isNamespacedVariableNameExpression(childNode)) {
pairs.push([childNode.expression, childNode]);
}
childNode.parent = parentNode;
}
}

/**
* Walk the full AST
*/
private walk() {
this.event.file.ast.walk((node, parent) => {
// link every child with its parent
node.parent = parent;
this.setParent(node, parent);

//do some file-based validations
if (isEnumStatement(node)) {
this.validateEnumDeclaration(node);
} else if (isNamespaceStatement(node)) {
//namespace names shouldn't be walked, but it needs its parent assigned
node.nameExpression.parent = parent;
} else if (isClassStatement(node)) {
//class extends names don't get walked, but it needs its parent
if (node.parentClassName) {
node.parentClassName.parent = parent;
}
} else if (isInterfaceStatement(node)) {
//class extends names don't get walked, but it needs its parent
if (node.parentInterfaceName) {
node.parentInterfaceName.parent = parent;
}
}
}, {
walkMode: WalkMode.visitAllRecursive
Expand Down
4 changes: 3 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { FunctionType } from './types/FunctionType';
import type { ParseMode } from './parser/Parser';
import type { Program, SourceObj, TranspileObj } from './Program';
import type { ProgramBuilder } from './ProgramBuilder';
import type { FunctionStatement } from './parser/Statement';
import type { FunctionStatement, Statement } from './parser/Statement';
import type { Expression } from './parser/Expression';
import type { TranspileState } from './parser/TranspileState';
import type { SourceMapGenerator, SourceNode } from 'source-map';
Expand Down Expand Up @@ -406,3 +406,5 @@ export interface FileLink<T> {
item: T;
file: BrsFile;
}

export type AstNode = Expression | Statement;
24 changes: 0 additions & 24 deletions src/parser/Expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,18 +348,6 @@ export class NamespacedVariableNameExpression extends Expression {
}
range: Range;

// @ts-expect-error override the property
public get parent() {
return this._parent;
}
public set parent(value) {
if (this.expression) {
this.expression.parent = value;
}
this._parent = value;
}
private _parent: Expression | Statement;

transpile(state: BrsTranspileState) {
return [
state.sourceNode(this, this.getName(ParseMode.BrightScript))
Expand Down Expand Up @@ -413,18 +401,6 @@ export class DottedGetExpression extends Expression {

public readonly range: Range;

// @ts-expect-error override the property
public get parent() {
return this._parent;
}
public set parent(value) {
if (this.obj) {
this.obj.parent = value;
}
this._parent = value;
}
private _parent: Expression | Statement;

transpile(state: BrsTranspileState) {
//if the callee starts with a namespace name, transpile the name
if (state.file.calleeStartsWithNamespace(this)) {
Expand Down

0 comments on commit 3f253e5

Please sign in to comment.