diff --git a/src/astUtils/creators.ts b/src/astUtils/creators.ts index e8db875a8..363518926 100644 --- a/src/astUtils/creators.ts +++ b/src/astUtils/creators.ts @@ -4,7 +4,7 @@ import { TokenKind } from '../lexer/TokenKind'; import type { Expression, NamespacedVariableNameExpression } from '../parser/Expression'; import { LiteralExpression, CallExpression, DottedGetExpression, VariableExpression, FunctionExpression } from '../parser/Expression'; import type { SGAttribute } from '../parser/SGTypes'; -import { Block, ClassMethodStatement } from '../parser/Statement'; +import { Block, MethodStatement } from '../parser/Statement'; /** * A range that points to the beginning of the file. Used to give non-null ranges to programmatically-added source code. @@ -148,15 +148,22 @@ export function createFunctionExpression(kind: TokenKind.Sub | TokenKind.Functio ); } -export function createClassMethodStatement(name: string, kind: TokenKind.Sub | TokenKind.Function = TokenKind.Function, accessModifier?: Token) { - return new ClassMethodStatement( - accessModifier, +export function createMethodStatement(name: string, kind: TokenKind.Sub | TokenKind.Function = TokenKind.Function, modifiers?: Token[]) { + return new MethodStatement( + modifiers, createIdentifier(name), createFunctionExpression(kind), null ); } +/** + * @deprecated use `createMethodStatement` + */ +export function createClassMethodStatement(name: string, kind: TokenKind.Sub | TokenKind.Function = TokenKind.Function, accessModifier?: Token) { + return createMethodStatement(name, kind, [accessModifier]); +} + export function createCall(callee: Expression, args?: Expression[], namespaceName?: NamespacedVariableNameExpression) { return new CallExpression( callee, diff --git a/src/astUtils/reflection.ts b/src/astUtils/reflection.ts index bb1c63442..5a95570d5 100644 --- a/src/astUtils/reflection.ts +++ b/src/astUtils/reflection.ts @@ -1,4 +1,4 @@ -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, Statement, InterfaceFieldStatement, InterfaceMethodStatement, InterfaceStatement, EnumStatement, EnumMemberStatement, TryCatchStatement, CatchStatement } from '../parser/Statement'; +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, Statement, InterfaceFieldStatement, InterfaceMethodStatement, InterfaceStatement, EnumStatement, EnumMemberStatement, TryCatchStatement, CatchStatement, MethodStatement, FieldStatement } from '../parser/Statement'; import type { LiteralExpression, Expression, BinaryExpression, CallExpression, FunctionExpression, NamespacedVariableNameExpression, DottedGetExpression, XmlAttributeGetExpression, IndexedGetExpression, GroupingExpression, EscapedCharCodeLiteralExpression, ArrayLiteralExpression, AALiteralExpression, UnaryExpression, VariableExpression, SourceLiteralExpression, NewExpression, CallfuncExpression, TemplateStringQuasiExpression, TemplateStringExpression, TaggedTemplateStringExpression, AnnotationExpression, FunctionParameterExpression, AAMemberExpression } from '../parser/Expression'; import type { BrsFile } from '../files/BrsFile'; import type { XmlFile } from '../files/XmlFile'; @@ -123,11 +123,25 @@ export function isClassStatement(element: Statement | Expression | undefined): e export function isImportStatement(element: Statement | Expression | undefined): element is ImportStatement { return element?.constructor?.name === 'ImportStatement'; } +export function isMethodStatement(element: Statement | Expression | undefined): element is MethodStatement { + const name = element?.constructor.name; + return name === 'MethodStatement' || name === 'ClassMethodStatement'; +} +/** + * @deprecated use `isMethodStatement` + */ export function isClassMethodStatement(element: Statement | Expression | undefined): element is ClassMethodStatement { - return element?.constructor.name === 'ClassMethodStatement'; + return isMethodStatement(element); } +export function isFieldStatement(element: Statement | Expression | undefined): element is FieldStatement { + const name = element?.constructor.name; + return name === 'FieldStatement' || name === 'ClassFieldStatement'; +} +/** + * @deprecated use `isFieldStatement` + */ export function isClassFieldStatement(element: Statement | Expression | undefined): element is ClassFieldStatement { - return element?.constructor.name === 'ClassFieldStatement'; + return isFieldStatement(element); } export function isInterfaceStatement(element: Statement | Expression | undefined): element is InterfaceStatement { return element?.constructor.name === 'InterfaceStatement'; diff --git a/src/astUtils/visitors.spec.ts b/src/astUtils/visitors.spec.ts index e41a03701..7c5d59f32 100644 --- a/src/astUtils/visitors.spec.ts +++ b/src/astUtils/visitors.spec.ts @@ -844,10 +844,10 @@ describe('astUtils visitors', () => { end class `, [ 'ClassStatement', - 'ClassFieldStatement', - 'ClassFieldStatement', + 'FieldStatement', + 'FieldStatement', 'LiteralExpression', - 'ClassMethodStatement', + 'MethodStatement', 'FunctionExpression', 'Block', 'ReturnStatement', diff --git a/src/astUtils/visitors.ts b/src/astUtils/visitors.ts index 277b9de15..56289c84b 100644 --- a/src/astUtils/visitors.ts +++ b/src/astUtils/visitors.ts @@ -1,6 +1,6 @@ /* eslint-disable no-bitwise */ import type { CancellationToken } from 'vscode-languageserver'; -import type { Statement, Body, AssignmentStatement, Block, ExpressionStatement, CommentStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, ClassMethodStatement, ClassFieldStatement, EnumStatement, EnumMemberStatement, DimStatement, TryCatchStatement, CatchStatement, ThrowStatement, InterfaceStatement, InterfaceFieldStatement, InterfaceMethodStatement } from '../parser/Statement'; +import type { Statement, Body, AssignmentStatement, Block, ExpressionStatement, CommentStatement, ExitForStatement, ExitWhileStatement, FunctionStatement, IfStatement, IncrementStatement, PrintStatement, GotoStatement, LabelStatement, ReturnStatement, EndStatement, StopStatement, ForStatement, ForEachStatement, WhileStatement, DottedSetStatement, IndexedSetStatement, LibraryStatement, NamespaceStatement, ImportStatement, ClassStatement, ClassMethodStatement, ClassFieldStatement, EnumStatement, EnumMemberStatement, DimStatement, TryCatchStatement, CatchStatement, ThrowStatement, InterfaceStatement, InterfaceFieldStatement, InterfaceMethodStatement, FieldStatement, MethodStatement } from '../parser/Statement'; import type { AALiteralExpression, AAMemberExpression, AnnotationExpression, ArrayLiteralExpression, BinaryExpression, CallExpression, CallfuncExpression, DottedGetExpression, EscapedCharCodeLiteralExpression, Expression, FunctionExpression, FunctionParameterExpression, GroupingExpression, IndexedGetExpression, LiteralExpression, NamespacedVariableNameExpression, NewExpression, NullCoalescingExpression, RegexLiteralExpression, SourceLiteralExpression, TaggedTemplateStringExpression, TemplateStringExpression, TemplateStringQuasiExpression, TernaryExpression, UnaryExpression, VariableExpression, XmlAttributeGetExpression } from '../parser/Expression'; import { isExpression, isStatement } from './reflection'; @@ -99,8 +99,16 @@ export function createVisitor( InterfaceFieldStatement?: (statement: InterfaceFieldStatement, parent?: Statement) => Statement | void; InterfaceMethodStatement?: (statement: InterfaceMethodStatement, parent?: Statement) => Statement | void; ClassStatement?: (statement: ClassStatement, parent?: Statement) => Statement | void; + /** + * @deprecated use `MethodStatement` + */ ClassMethodStatement?: (statement: ClassMethodStatement, parent?: Statement) => Statement | void; + /** + * @deprecated use `FieldStatement` + */ ClassFieldStatement?: (statement: ClassFieldStatement, parent?: Statement) => Statement | void; + MethodStatement?: (statement: MethodStatement, parent?: Statement) => Statement | void; + FieldStatement?: (statement: FieldStatement, parent?: Statement) => Statement | void; TryCatchStatement?: (statement: TryCatchStatement, parent?: Statement) => Statement | void; CatchStatement?: (statement: CatchStatement, parent?: Statement) => Statement | void; ThrowStatement?: (statement: ThrowStatement, parent?: Statement) => Statement | void; @@ -135,6 +143,13 @@ export function createVisitor( RegexLiteralExpression?: (expression: RegexLiteralExpression, parent?: Statement | Expression) => Expression | void; } ) { + //remap some deprecated visitor names TODO remove this in v1 + if (visitor.ClassFieldStatement) { + visitor.FieldStatement = visitor.ClassFieldStatement; + } + if (visitor.ClassMethodStatement) { + visitor.MethodStatement = visitor.ClassMethodStatement; + } return ((statement: Statement, parent?: Statement): Statement | void => { return visitor[statement.constructor.name]?.(statement, parent); }); diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts index 9289076ef..e6687057c 100644 --- a/src/files/BrsFile.ts +++ b/src/files/BrsFile.ts @@ -13,7 +13,7 @@ import { Lexer } from '../lexer/Lexer'; import { TokenKind, AllowedLocalIdentifiers, Keywords } from '../lexer/TokenKind'; import { Parser, ParseMode } from '../parser/Parser'; import type { FunctionExpression, VariableExpression, Expression } from '../parser/Expression'; -import type { ClassStatement, FunctionStatement, NamespaceStatement, ClassMethodStatement, AssignmentStatement, LibraryStatement, ImportStatement, Statement, ClassFieldStatement } from '../parser/Statement'; +import type { ClassStatement, FunctionStatement, NamespaceStatement, AssignmentStatement, LibraryStatement, ImportStatement, Statement, MethodStatement, FieldStatement } from '../parser/Statement'; import type { Program, SignatureInfoObj } from '../Program'; import { DynamicType } from '../types/DynamicType'; import { FunctionType } from '../types/FunctionType'; @@ -1497,12 +1497,12 @@ export class BrsFile { public getClassMemberDefinitions(textToSearchFor: string, file: BrsFile): Location[] { let results: Location[] = []; //get class fields and members - const statementHandler = (statement: ClassMethodStatement) => { + const statementHandler = (statement: MethodStatement) => { if (statement.getName(file.parseMode).toLowerCase() === textToSearchFor) { results.push(Location.create(util.pathToUri(file.srcPath), statement.range)); } }; - const fieldStatementHandler = (statement: ClassFieldStatement) => { + const fieldStatementHandler = (statement: FieldStatement) => { if (statement.name.text.toLowerCase() === textToSearchFor) { results.push(Location.create(util.pathToUri(file.srcPath), statement.range)); } @@ -1698,7 +1698,7 @@ export class BrsFile { return { key: key, signature: signature, index: index }; } - private getClassMethod(classStatement: ClassStatement, name: string, walkParents = true): ClassMethodStatement | undefined { + private getClassMethod(classStatement: ClassStatement, name: string, walkParents = true): MethodStatement | undefined { //TODO - would like to write this with getClassHieararchy; but got stuck on working out the scopes to use... :( let statement; const statementHandler = (e) => { @@ -1708,7 +1708,7 @@ export class BrsFile { }; while (classStatement) { classStatement.walk(createVisitor({ - ClassMethodStatement: statementHandler + MethodStatement: statementHandler }), { walkMode: WalkMode.visitStatements }); diff --git a/src/parser/Parser.Class.spec.ts b/src/parser/Parser.Class.spec.ts index 6867e2d30..e8264ab7f 100644 --- a/src/parser/Parser.Class.spec.ts +++ b/src/parser/Parser.Class.spec.ts @@ -3,7 +3,7 @@ import { DiagnosticMessages } from '../DiagnosticMessages'; import { TokenKind, AllowedLocalIdentifiers, AllowedProperties } from '../lexer/TokenKind'; import { Lexer } from '../lexer/Lexer'; import { Parser, ParseMode } from './Parser'; -import type { FunctionStatement, AssignmentStatement, ClassFieldStatement } from './Statement'; +import type { FunctionStatement, AssignmentStatement, FieldStatement } from './Statement'; import { ClassStatement } from './Statement'; import { NewExpression } from './Expression'; @@ -193,7 +193,7 @@ describe('parser class', () => { let { statements, diagnostics } = Parser.parse(tokens, { mode: ParseMode.BrighterScript }); expect(diagnostics).to.be.empty; expect(statements[0]).instanceof(ClassStatement); - let field = (statements[0] as ClassStatement).body[0] as ClassFieldStatement; + let field = (statements[0] as ClassStatement).body[0] as FieldStatement; expect(field.accessModifier.kind).to.equal(TokenKind.Public); expect(field.name.text).to.equal('firstName'); expect(field.as.text).to.equal('as'); @@ -315,10 +315,10 @@ describe('parser class', () => { let { statements, diagnostics } = Parser.parse(tokens, { mode: ParseMode.BrighterScript }); expect(diagnostics[0]?.message).not.to.exist; let cls = statements[0] as ClassStatement; - expect((cls.memberMap['name'] as ClassFieldStatement).initialValue).to.exist; - expect((cls.memberMap['age'] as ClassFieldStatement).initialValue).to.exist; - expect((cls.memberMap['isalive'] as ClassFieldStatement).initialValue).to.exist; - expect((cls.memberMap['callback'] as ClassFieldStatement).initialValue).to.exist; + expect((cls.memberMap['name'] as FieldStatement).initialValue).to.exist; + expect((cls.memberMap['age'] as FieldStatement).initialValue).to.exist; + expect((cls.memberMap['isalive'] as FieldStatement).initialValue).to.exist; + expect((cls.memberMap['callback'] as FieldStatement).initialValue).to.exist; }); it('detects missing function keyword', () => { diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts index 9c2fc59f8..d95dd8fe3 100644 --- a/src/parser/Parser.ts +++ b/src/parser/Parser.ts @@ -4,38 +4,35 @@ import type { BlockTerminator } from '../lexer/TokenKind'; import { Lexer } from '../lexer/Lexer'; import { AllowedLocalIdentifiers, - AssignmentOperators, - DisallowedLocalIdentifiersText, - DisallowedFunctionIdentifiersText, AllowedProperties, + AssignmentOperators, BrighterScriptSourceLiterals, - DeclarableTypes, TokenKind + DeclarableTypes, + DisallowedFunctionIdentifiersText, + DisallowedLocalIdentifiersText, + TokenKind } from '../lexer/TokenKind'; import type { - Statement, + PrintSeparatorSpace, PrintSeparatorTab, - PrintSeparatorSpace + Statement } from './Statement'; import { - InterfaceStatement, - InterfaceMethodStatement, - InterfaceFieldStatement, AssignmentStatement, Block, Body, CatchStatement, - ClassFieldStatement, - ClassMethodStatement, ClassStatement, CommentStatement, DimStatement, DottedSetStatement, EndStatement, - EnumStatement, EnumMemberStatement, + EnumStatement, ExitForStatement, ExitWhileStatement, ExpressionStatement, + FieldStatement, ForEachStatement, ForStatement, FunctionStatement, @@ -44,8 +41,12 @@ import { ImportStatement, IncrementStatement, IndexedSetStatement, + InterfaceFieldStatement, + InterfaceMethodStatement, + InterfaceStatement, LabelStatement, LibraryStatement, + MethodStatement, NamespaceStatement, PrintStatement, ReturnStatement, @@ -61,30 +62,30 @@ import type { Expression } from './Expression'; import { AALiteralExpression, AAMemberExpression, + AnnotationExpression, ArrayLiteralExpression, BinaryExpression, CallExpression, CallfuncExpression, DottedGetExpression, + EscapedCharCodeLiteralExpression, FunctionExpression, + FunctionParameterExpression, GroupingExpression, IndexedGetExpression, LiteralExpression, NamespacedVariableNameExpression, NewExpression, + NullCoalescingExpression, RegexLiteralExpression, - UnaryExpression, - VariableExpression, - XmlAttributeGetExpression, + SourceLiteralExpression, + TaggedTemplateStringExpression, TemplateStringExpression, - EscapedCharCodeLiteralExpression, TemplateStringQuasiExpression, - TaggedTemplateStringExpression, - SourceLiteralExpression, - AnnotationExpression, - FunctionParameterExpression, TernaryExpression, - NullCoalescingExpression + UnaryExpression, + VariableExpression, + XmlAttributeGetExpression } from './Expression'; import type { Diagnostic, Range } from 'vscode-languageserver'; import { Logger } from '../Logger'; @@ -647,7 +648,7 @@ export class Parser { }); } - decl = new ClassMethodStatement( + decl = new MethodStatement( accessModifier, funcDeclaration.name, funcDeclaration.func, @@ -655,12 +656,12 @@ export class Parser { ); //refer to this statement as parent of the expression - functionStatement.func.functionStatement = decl as ClassMethodStatement; + functionStatement.func.functionStatement = decl as MethodStatement; //fields } else if (this.checkAny(TokenKind.Identifier, ...AllowedProperties)) { - decl = this.classFieldDeclaration(accessModifier); + decl = this.fieldDeclaration(accessModifier); //class fields cannot be overridden if (overrideKeyword) { @@ -711,7 +712,7 @@ export class Parser { return result; } - private classFieldDeclaration(accessModifier: Token | null) { + private fieldDeclaration(accessModifier: Token | null) { let name = this.consume( DiagnosticMessages.expectedClassFieldIdentifier(), TokenKind.Identifier, @@ -741,7 +742,7 @@ export class Parser { initialValue = this.expression(); } - return new ClassFieldStatement( + return new FieldStatement( accessModifier, name, asToken, diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts index 2829e860b..198ec5b2a 100644 --- a/src/parser/Statement.ts +++ b/src/parser/Statement.ts @@ -12,7 +12,7 @@ import type { WalkVisitor, WalkOptions } from '../astUtils/visitors'; import { InternalWalkMode, walk, createVisitor, WalkMode } from '../astUtils/visitors'; import { isCallExpression, isClassFieldStatement, isClassMethodStatement, isCommentStatement, isEnumMemberStatement, isExpression, isExpressionStatement, isFunctionStatement, isIfStatement, isInterfaceFieldStatement, isInterfaceMethodStatement, isInvalidType, isLiteralExpression, isTypedefProvider, isVoidType } from '../astUtils/reflection'; import type { TranspileResult, TypedefProvider } from '../interfaces'; -import { createClassMethodStatement, createInvalidLiteral, createToken, interpolatedRange } from '../astUtils/creators'; +import { createInvalidLiteral, createMethodStatement, createToken, interpolatedRange } from '../astUtils/creators'; import { DynamicType } from '../types/DynamicType'; import type { BscType } from '../types/BscType'; import type { SourceNode } from 'source-map'; @@ -1554,10 +1554,9 @@ export class ClassStatement extends Statement implements TypedefProvider { } } - public memberMap = {} as Record; - public methods = [] as ClassMethodStatement[]; - public fields = [] as ClassFieldStatement[]; - + public memberMap = {} as Record; + public methods = [] as MethodStatement[]; + public fields = [] as FieldStatement[]; public readonly range: Range; @@ -1679,13 +1678,13 @@ export class ClassStatement extends Statement implements TypedefProvider { for (let key in this.memberMap) { let member = this.memberMap[key]; if (member.name?.text?.toLowerCase() === 'new') { - return member as ClassMethodStatement; + return member as MethodStatement; } } } private getEmptyNewFunction() { - return createClassMethodStatement('new', TokenKind.Sub); + return createMethodStatement('new', TokenKind.Sub); } /** @@ -1871,20 +1870,38 @@ export class ClassStatement extends Statement implements TypedefProvider { } } -export class ClassMethodStatement extends FunctionStatement { +const accessModifiers = [ + TokenKind.Public, + TokenKind.Protected, + TokenKind.Private +]; +export class MethodStatement extends FunctionStatement { constructor( - public accessModifier: Token, + modifiers: Token | Token[], name: Identifier, func: FunctionExpression, public override: Token ) { super(name, func, undefined); + if (modifiers) { + if (Array.isArray(modifiers)) { + this.modifiers.push(...modifiers); + } else { + this.modifiers.push(modifiers); + } + } this.range = util.createRangeFromPositions( (this.accessModifier ?? this.func).range.start, this.func.range.end ); } + public modifiers: Token[] = []; + + public get accessModifier() { + return this.modifiers.find(x => accessModifiers.includes(x.kind)); + } + public readonly range: Range; transpile(state: BrsTranspileState) { @@ -2035,8 +2052,12 @@ export class ClassMethodStatement extends FunctionStatement { } } } +/** + * @deprecated use `MethodStatement` + */ +export class ClassMethodStatement extends MethodStatement { } -export class ClassFieldStatement extends Statement implements TypedefProvider { +export class FieldStatement extends Statement implements TypedefProvider { constructor( readonly accessModifier?: Token, @@ -2106,7 +2127,17 @@ export class ClassFieldStatement extends Statement implements TypedefProvider { } } } -export type ClassMemberStatement = ClassFieldStatement | ClassMethodStatement; +/** + * @deprecated use `FieldStatement` + */ +export class ClassFieldStatement extends FieldStatement { } + +export type MemberStatement = FieldStatement | MethodStatement; + +/** + * @deprecated use `MemeberStatement` + */ +export type ClassMemberStatement = MemberStatement; export class TryCatchStatement extends Statement { constructor( diff --git a/src/validators/ClassValidator.ts b/src/validators/ClassValidator.ts index 8b3a8eefc..b3c088898 100644 --- a/src/validators/ClassValidator.ts +++ b/src/validators/ClassValidator.ts @@ -2,7 +2,7 @@ import type { Scope } from '../Scope'; import { DiagnosticMessages } from '../DiagnosticMessages'; import type { CallExpression } from '../parser/Expression'; import { ParseMode } from '../parser/Parser'; -import type { ClassMethodStatement, ClassStatement } from '../parser/Statement'; +import type { ClassStatement, MethodStatement } from '../parser/Statement'; import { CancellationTokenSource, Location } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import util from '../util'; @@ -111,7 +111,7 @@ export class BsClassValidator { private verifyChildConstructor() { for (const [, classStatement] of this.classes) { - const newMethod = classStatement.memberMap.new as ClassMethodStatement; + const newMethod = classStatement.memberMap.new as MethodStatement; if ( //this class has a "new method"