From 7c270fdb79fc49f44e16769444317da863f45bc6 Mon Sep 17 00:00:00 2001 From: Joseph Junker Date: Sat, 13 Jan 2024 23:28:26 -0800 Subject: [PATCH 1/2] Reduce null errors 906 -> 601 --- src/bscPlugin/hover/HoverProcessor.ts | 3 +- src/files/BrsFile.ts | 8 +- src/files/XmlFile.ts | 3 +- src/interfaces.ts | 11 +- src/parser/AstNode.ts | 2 +- src/parser/BrsTranspileState.ts | 2 +- src/parser/Expression.ts | 122 ++++++++-------- src/parser/Parser.spec.ts | 2 +- src/parser/Statement.ts | 193 +++++++++++++------------- src/parser/TranspileState.ts | 6 +- src/util.ts | 38 ++++- 11 files changed, 216 insertions(+), 174 deletions(-) diff --git a/src/bscPlugin/hover/HoverProcessor.ts b/src/bscPlugin/hover/HoverProcessor.ts index 17a7d8209..b0987f060 100644 --- a/src/bscPlugin/hover/HoverProcessor.ts +++ b/src/bscPlugin/hover/HoverProcessor.ts @@ -1,4 +1,3 @@ -import { SourceNode } from 'source-map'; import { isBrsFile, isFunctionType, isXmlFile } from '../../astUtils/reflection'; import type { BrsFile } from '../../files/BrsFile'; import type { XmlFile } from '../../files/XmlFile'; @@ -67,7 +66,7 @@ export class HoverProcessor { //find a constant with this name const constant = scope?.getConstFileLink(fullName, containingNamespace); if (constant) { - const constantValue = new SourceNode(null, null, null, constant.item.value.transpile(new BrsTranspileState(file))).toString(); + const constantValue = util.sourceNodeFromTranspileResult(null, null, null, constant.item.value.transpile(new BrsTranspileState(file))).toString(); return { contents: this.buildContentsWithDocs(fence(`const ${constant.item.fullName} = ${constantValue}`), constant.item.tokens.const), range: token.range diff --git a/src/files/BrsFile.ts b/src/files/BrsFile.ts index 19c74a567..e95d17435 100644 --- a/src/files/BrsFile.ts +++ b/src/files/BrsFile.ts @@ -1275,7 +1275,7 @@ export class BrsFile { /** * Determine if the callee (i.e. function name) is a known function declared on the given namespace. */ - public calleeIsKnownNamespaceFunction(callee: Expression, namespaceName: string) { + public calleeIsKnownNamespaceFunction(callee: Expression, namespaceName: string | undefined) { //if we have a variable and a namespace if (isVariableExpression(callee) && namespaceName) { let lowerCalleeName = callee?.name?.text?.toLowerCase(); @@ -1283,7 +1283,7 @@ export class BrsFile { let scopes = this.program.getScopesForFile(this); for (let scope of scopes) { let namespace = scope.namespaceLookup.get(namespaceName.toLowerCase()); - if (namespace.functionStatements[lowerCalleeName]) { + if (namespace?.functionStatements[lowerCalleeName]) { return true; } } @@ -1676,7 +1676,7 @@ export class BrsFile { let transpileResult: SourceNode | undefined; if (this.needsTranspiled) { - transpileResult = new SourceNode(null, null, state.srcPath, this.ast.transpile(state)); + transpileResult = util.sourceNodeFromTranspileResult(null, null, state.srcPath, this.ast.transpile(state)); } else if (this.program.options.sourceMap) { //emit code as-is with a simple map to the original file location transpileResult = util.simpleMap(state.srcPath, this.fileContents); @@ -1704,7 +1704,7 @@ export class BrsFile { public getTypedef() { const state = new BrsTranspileState(this); const typedef = this.ast.getTypedef(state); - const programNode = new SourceNode(null, null, this.srcPath, typedef); + const programNode = util.sourceNodeFromTranspileResult(null, null, this.srcPath, typedef); return programNode.toString(); } diff --git a/src/files/XmlFile.ts b/src/files/XmlFile.ts index cb7af0544..e3172998a 100644 --- a/src/files/XmlFile.ts +++ b/src/files/XmlFile.ts @@ -505,7 +505,8 @@ export class XmlFile { //temporarily add the missing imports as script tags this.ast.component.scripts = publishableScripts; - transpileResult = new SourceNode(null, null, state.srcPath, this.parser.ast.transpile(state)); + + transpileResult = util.sourceNodeFromTranspileResult(null, null, state.srcPath, this.parser.ast.transpile(state)); //restore the original scripts array this.ast.component.scripts = originalScripts; diff --git a/src/interfaces.ts b/src/interfaces.ts index 9ac1a2b7b..2ade734f5 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -392,10 +392,17 @@ export interface SemanticToken { } export interface TypedefProvider { - getTypedef(state: TranspileState): Array; + getTypedef(state: TranspileState): TranspileResult; } -export type TranspileResult = Array<(string | SourceNode)>; +export type TranspileResult = Array<(string | SourceNode | TranspileResult)>; + +/** + * This is the type that the SourceNode class is declared as taking in its constructor. + * The actual type that SourceNode accepts is the more permissive TranspileResult, but + * we need to use this declared type for some type casts. + */ +export type FlattenedTranspileResult = Array; export type FileResolver = (srcPath: string) => string | undefined | Thenable | void; diff --git a/src/parser/AstNode.ts b/src/parser/AstNode.ts index 7df01402e..affa32b33 100644 --- a/src/parser/AstNode.ts +++ b/src/parser/AstNode.ts @@ -16,7 +16,7 @@ export abstract class AstNode { /** * The starting and ending location of the node. */ - public abstract range: Range; + public abstract range: Range | undefined; public abstract transpile(state: BrsTranspileState): TranspileResult; diff --git a/src/parser/BrsTranspileState.ts b/src/parser/BrsTranspileState.ts index a6fd3b3b7..28ae56c23 100644 --- a/src/parser/BrsTranspileState.ts +++ b/src/parser/BrsTranspileState.ts @@ -22,7 +22,7 @@ export class BrsTranspileState extends TranspileState { * Used to assist blocks in knowing when to add a comment statement to the same line as the first line of the parent */ lineage = [] as Array<{ - range: Range; + range?: Range; }>; /** diff --git a/src/parser/Expression.ts b/src/parser/Expression.ts index acab57dc6..af9b9149b 100644 --- a/src/parser/Expression.ts +++ b/src/parser/Expression.ts @@ -29,10 +29,10 @@ export class BinaryExpression extends Expression { public right: Expression ) { super(); - this.range = util.createRangeFromPositions(this.left.range.start, this.right.range.end); + this.range = util.createRangeFromPositionsOptional(this.left.range?.start, this.right.range?.end); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { return [ @@ -69,7 +69,7 @@ export class CallExpression extends Expression { this.range = util.createBoundingRange(this.callee, this.openingParen, ...args, this.closingParen); } - public readonly range: Range; + public readonly range: Range | undefined; /** * Get the name of the wrapping namespace (if it exists) @@ -80,7 +80,7 @@ export class CallExpression extends Expression { } transpile(state: BrsTranspileState, nameOverride?: string) { - let result: Array = []; + let result: TranspileResult = []; //transpile the name if (nameOverride) { @@ -207,10 +207,10 @@ export class FunctionExpression extends Expression implements TypedefProvider { } transpile(state: BrsTranspileState, name?: Identifier, includeBody = true) { - let results = []; + let results = [] as TranspileResult; //'function'|'sub' results.push( - state.transpileToken(this.functionType) + state.transpileToken(this.functionType!) ); //functionName? if (name) { @@ -245,7 +245,7 @@ export class FunctionExpression extends Expression implements TypedefProvider { state.transpileToken(this.asToken), ' ', //return type - state.sourceNode(this.returnTypeToken, this.returnType.toTypeString()) + state.sourceNode(this.returnTypeToken!, this.returnType.toTypeString()) ); } if (includeBody) { @@ -309,7 +309,7 @@ export class FunctionExpression extends Expression implements TypedefProvider { getFunctionType(): FunctionType { let functionType = new FunctionType(this.returnType); - functionType.isSub = this.functionType.text === 'sub'; + functionType.isSub = this.functionType?.text === 'sub'; for (let param of this.parameters) { functionType.addParameter(param.name.text, param.type, !!param.typeToken); } @@ -334,7 +334,7 @@ export class FunctionParameterExpression extends Expression { public type: BscType; - public get range(): Range { + public get range(): Range | undefined { return util.createBoundingRange( this.name, this.asToken, @@ -358,27 +358,30 @@ export class FunctionParameterExpression extends Expression { result.push(' '); result.push(state.transpileToken(this.asToken)); result.push(' '); - result.push(state.sourceNode(this.typeToken, this.type.toTypeString())); + result.push(state.sourceNode(this.typeToken!, this.type.toTypeString())); } return result; } public getTypedef(state: BrsTranspileState): TranspileResult { - return [ - //name - this.name.text, - //default value - ...(this.defaultValue ? [ - ' = ', - ...this.defaultValue.transpile(state) - ] : []), - //type declaration - ...(this.asToken ? [ - ' as ', - this.typeToken?.text - ] : []) - ]; + const results = [this.name.text] as TranspileResult; + + if (this.defaultValue) { + results.push(' = ', ...this.defaultValue.transpile(state)); + } + + if (this.asToken) { + results.push(' as '); + + // TODO: Is this conditional needed? Will typeToken always exist + // so long as `asToken` exists? + if (this.typeToken) { + results.push(this.typeToken.text); + } + } + + return results; } walk(visitor: WalkVisitor, options: WalkOptions) { @@ -397,7 +400,7 @@ export class NamespacedVariableNameExpression extends Expression { super(); this.range = expression.range; } - range: Range; + range: Range | undefined; transpile(state: BrsTranspileState) { return [ @@ -451,7 +454,7 @@ export class DottedGetExpression extends Expression { this.range = util.createBoundingRange(this.obj, this.dot, this.name); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { //if the callee starts with a namespace name, transpile the name @@ -487,7 +490,7 @@ export class XmlAttributeGetExpression extends Expression { this.range = util.createBoundingRange(this.obj, this.at, this.name); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { return [ @@ -519,7 +522,7 @@ export class IndexedGetExpression extends Expression { this.range = util.createBoundingRange(this.obj, this.openingSquare, this.questionDotToken, this.openingSquare, this.index, this.closingSquare); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { return [ @@ -551,7 +554,7 @@ export class GroupingExpression extends Expression { this.range = util.createBoundingRange(this.tokens.left, this.expression, this.tokens.right); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { return [ @@ -646,10 +649,10 @@ export class ArrayLiteralExpression extends Expression { this.range = util.createBoundingRange(this.open, ...this.elements, this.close); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; result.push( state.transpileToken(this.open) ); @@ -714,7 +717,7 @@ export class AAMemberExpression extends Expression { this.range = util.createBoundingRange(this.keyToken, this.colonToken, this.value); } - public range: Range; + public range: Range | undefined; public commaToken?: Token; transpile(state: BrsTranspileState) { @@ -738,10 +741,10 @@ export class AALiteralExpression extends Expression { this.range = util.createBoundingRange(this.open, ...this.elements, this.close); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; //open curly result.push( state.transpileToken(this.open) @@ -788,7 +791,7 @@ export class AALiteralExpression extends Expression { //if next element is a same-line comment, skip the newline - if (nextElement && isCommentStatement(nextElement) && nextElement.range.start.line === element.range.start.line) { + if (nextElement && isCommentStatement(nextElement) && nextElement.range?.start.line === element.range?.start.line) { //add a newline between statements } else { @@ -826,20 +829,19 @@ export class UnaryExpression extends Expression { this.range = util.createBoundingRange(this.operator, this.right); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let separatingWhitespace: string; + let separatingWhitespace: string | undefined; if (isVariableExpression(this.right)) { separatingWhitespace = this.right.name.leadingWhitespace; } else if (isLiteralExpression(this.right)) { separatingWhitespace = this.right.token.leadingWhitespace; - } else { - separatingWhitespace = ' '; } + return [ state.transpileToken(this.operator), - separatingWhitespace, + separatingWhitespace ?? ' ', ...this.right.transpile(state) ]; } @@ -866,10 +868,10 @@ export class VariableExpression extends Expression { } transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; const namespace = this.findAncestor(isNamespaceStatement); //if the callee is the name of a known namespace function - if (state.file.calleeIsKnownNamespaceFunction(this, namespace?.getName(ParseMode.BrighterScript))) { + if (namespace && state.file.calleeIsKnownNamespaceFunction(this, namespace.getName(ParseMode.BrighterScript))) { result.push( state.sourceNode(this, [ namespace.getName(ParseMode.BrightScript), @@ -904,7 +906,7 @@ export class SourceLiteralExpression extends Expression { private getFunctionName(state: BrsTranspileState, parseMode: ParseMode) { let func = state.file.getFunctionScopeAtPosition(this.token.range.start).func; - let nameParts = []; + let nameParts = [] as TranspileResult; while (func.parentFunction) { let index = func.parentFunction.childFunctionExpressions.indexOf(func); nameParts.unshift(`anon${index}`); @@ -912,7 +914,7 @@ export class SourceLiteralExpression extends Expression { } //get the index of this function in its parent nameParts.unshift( - func.functionStatement.getName(parseMode) + func.functionStatement!.getName(parseMode) ); return nameParts.join('$'); } @@ -991,7 +993,7 @@ export class NewExpression extends Expression { return this.call.callee as NamespacedVariableNameExpression; } - public readonly range: Range; + public readonly range: Range | undefined; public transpile(state: BrsTranspileState) { const namespace = this.findAncestor(isNamespaceStatement); @@ -1031,7 +1033,7 @@ export class CallfuncExpression extends Expression { ); } - public readonly range: Range; + public readonly range: Range | undefined; /** * Get the name of the wrapping namespace (if it exists) @@ -1042,7 +1044,7 @@ export class CallfuncExpression extends Expression { } public transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; result.push( ...this.callee.transpile(state), state.sourceNode(this.operator, '.callfunc'), @@ -1092,10 +1094,10 @@ export class TemplateStringQuasiExpression extends Expression { ...expressions ); } - readonly range: Range; + readonly range: Range | undefined; transpile(state: BrsTranspileState, skipEmptyStrings = true) { - let result = []; + let result = [] as TranspileResult; let plus = ''; for (let expression of this.expressions) { //skip empty strings @@ -1135,7 +1137,7 @@ export class TemplateStringExpression extends Expression { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { if (this.quasis.length === 1 && this.expressions.length === 0) { @@ -1223,10 +1225,10 @@ export class TaggedTemplateStringExpression extends Expression { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; result.push( state.transpileToken(this.tagName), '([' @@ -1300,7 +1302,7 @@ export class AnnotationExpression extends Expression { } public name: string; - public call: CallExpression; + public call: CallExpression | undefined; /** * Convert annotation arguments to JavaScript types @@ -1347,12 +1349,12 @@ export class TernaryExpression extends Expression { ); } - public range: Range; + public range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; - let consequentInfo = util.getExpressionInfo(this.consequent); - let alternateInfo = util.getExpressionInfo(this.alternate); + let result = [] as TranspileResult; + let consequentInfo = util.getExpressionInfo(this.consequent!); + let alternateInfo = util.getExpressionInfo(this.alternate!); //get all unique variable names used in the consequent and alternate, and sort them alphabetically so the output is consistent let allUniqueVarNames = [...new Set([...consequentInfo.uniqueVarNames, ...alternateInfo.uniqueVarNames])].sort(); @@ -1430,10 +1432,10 @@ export class NullCoalescingExpression extends Expression { alternate ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; let consequentInfo = util.getExpressionInfo(this.consequent); let alternateInfo = util.getExpressionInfo(this.alternate); @@ -1544,7 +1546,7 @@ export class RegexLiteralExpression extends Expression { } // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style -type ExpressionValue = string | number | boolean | Expression | ExpressionValue[] | { [key: string]: ExpressionValue }; +type ExpressionValue = string | number | boolean | Expression | ExpressionValue[] | { [key: string]: ExpressionValue } | null; function expressionToValue(expr: Expression, strict: boolean): ExpressionValue { if (!expr) { diff --git a/src/parser/Parser.spec.ts b/src/parser/Parser.spec.ts index 264962dc2..c47cdcb03 100644 --- a/src/parser/Parser.spec.ts +++ b/src/parser/Parser.spec.ts @@ -53,7 +53,7 @@ describe('parser', () => { return [...expressions.values()].map(x => { const file = new BrsFile('', '', new Program({} as any)); const state = new BrsTranspileState(file); - return new SourceNode(null, null, null, x.transpile(state)).toString(); + return new SourceNode(null, null, null, x.transpile(state) as any).toString(); }); } diff --git a/src/parser/Statement.ts b/src/parser/Statement.ts index 6f687b0e9..7f85fa8b3 100644 --- a/src/parser/Statement.ts +++ b/src/parser/Statement.ts @@ -14,7 +14,6 @@ import type { TranspileResult, TypedefProvider } from '../interfaces'; 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'; import type { TranspileState } from './TranspileState'; import { SymbolTable } from '../SymbolTable'; import type { Expression } from './AstNode'; @@ -68,7 +67,7 @@ export class Body extends Statement implements TypedefProvider { //this is the first statement. do nothing related to spacing and newlines //if comment is on same line as prior sibling - } else if (isCommentStatement(statement) && previousStatement && statement.range.start.line === previousStatement.range.end.line) { + } else if (isCommentStatement(statement) && previousStatement && statement.range?.start.line === previousStatement.range?.end.line) { result.push( ' ' ); @@ -90,8 +89,8 @@ export class Body extends Statement implements TypedefProvider { return result; } - getTypedef(state: BrsTranspileState) { - let result = []; + getTypedef(state: BrsTranspileState): TranspileResult { + let result = [] as TranspileResult; for (const statement of this.statements) { //if the current statement supports generating typedef, call it if (isTypedefProvider(statement)) { @@ -122,7 +121,7 @@ export class AssignmentStatement extends Statement { this.range = util.createBoundingRange(name, equals, value); } - public readonly range: Range; + public readonly range: Range | undefined; /** * Get the name of the wrapping namespace (if it exists) @@ -166,7 +165,7 @@ export class Block extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { state.blockDepth++; @@ -216,7 +215,7 @@ export class ExpressionStatement extends Statement { this.range = this.expression.range; } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { return this.expression.transpile(state); @@ -242,14 +241,14 @@ export class CommentStatement extends Statement implements Expression, TypedefPr } } - public range: Range; + public range: Range | undefined; get text() { return this.comments.map(x => x.text).join('\n'); } transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; for (let i = 0; i < this.comments.length; i++) { let comment = this.comments[i]; if (i > 0) { @@ -331,7 +330,7 @@ export class FunctionStatement extends Statement implements TypedefProvider { this.range = this.func.range; } - public readonly range: Range; + public readonly range: Range | undefined; /** * Get the name of this expression based on the parse mode @@ -366,7 +365,7 @@ export class FunctionStatement extends Statement implements TypedefProvider { } getTypedef(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; for (let annotation of this.annotations ?? []) { result.push( ...annotation.getTypedef(state), @@ -412,10 +411,10 @@ export class IfStatement extends Statement { tokens.endIf ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let results = []; + let results = [] as TranspileResult; //if (already indented by block) results.push(state.transpileToken(this.tokens.if)); results.push(' '); @@ -456,7 +455,7 @@ export class IfStatement extends Statement { if (body.length > 0) { //zero or more spaces between the `else` and the `if` - results.push(this.elseBranch.tokens.if.leadingWhitespace); + results.push(this.elseBranch.tokens.if.leadingWhitespace!); results.push(...body); // stop here because chained if will transpile the rest @@ -467,7 +466,7 @@ export class IfStatement extends Statement { } else { //else body - state.lineage.unshift(this.tokens.else); + state.lineage.unshift(this.tokens.else!); let body = this.elseBranch.transpile(state); state.lineage.shift(); @@ -515,7 +514,7 @@ export class IncrementStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { return [ @@ -564,13 +563,13 @@ export class PrintStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { let result = [ state.transpileToken(this.tokens.print), ' ' - ]; + ] as TranspileResult; for (let i = 0; i < this.expressions.length; i++) { const expressionOrSeparator: any = this.expressions[i]; if (expressionOrSeparator.transpile) { @@ -613,29 +612,29 @@ export class DimStatement extends Statement { closingSquare ); } - public range: Range; + public range: Range | undefined; public transpile(state: BrsTranspileState) { let result = [ state.transpileToken(this.dimToken), ' ', - state.transpileToken(this.identifier), - state.transpileToken(this.openingSquare) - ]; - for (let i = 0; i < this.dimensions.length; i++) { + state.transpileToken(this.identifier!), + state.transpileToken(this.openingSquare!) + ] as TranspileResult; + for (let i = 0; i < this.dimensions!.length; i++) { if (i > 0) { result.push(', '); } result.push( - ...this.dimensions[i].transpile(state) + ...this.dimensions![i].transpile(state) ); } - result.push(state.transpileToken(this.closingSquare)); + result.push(state.transpileToken(this.closingSquare!)); return result; } public walk(visitor: WalkVisitor, options: WalkOptions) { - if (this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) { + if (this.dimensions?.length !== undefined && this.dimensions?.length > 0 && options.walkMode & InternalWalkMode.walkExpressions) { walkArray(this.dimensions, visitor, options, this); } @@ -656,7 +655,7 @@ export class GotoStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { return [ @@ -685,7 +684,7 @@ export class LabelStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { return [ @@ -714,10 +713,10 @@ export class ReturnStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; result.push( state.transpileToken(this.tokens.return) ); @@ -805,10 +804,10 @@ export class ForStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; //for result.push( state.transpileToken(this.forToken), @@ -832,7 +831,7 @@ export class ForStatement extends Statement { ' ', state.transpileToken(this.stepToken), ' ', - this.increment.transpile(state) + this.increment!.transpile(state) ); } //loop body @@ -888,10 +887,10 @@ export class ForEachStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; //for each result.push( state.transpileToken(this.tokens.forEach), @@ -953,10 +952,10 @@ export class WhileStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; //while result.push( state.transpileToken(this.tokens.while), @@ -1009,7 +1008,7 @@ export class DottedSetStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { //if the value is a compound assignment, don't add the obj, dot, name, or operator...the expression will handle that @@ -1055,7 +1054,7 @@ export class IndexedSetStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { //if the value is a component assignment, don't add the obj, index or operator...the expression will handle that @@ -1102,10 +1101,10 @@ export class LibraryStatement extends Statement implements TypedefProvider { ); } - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; result.push( state.transpileToken(this.tokens.library) ); @@ -1149,7 +1148,7 @@ export class NamespaceStatement extends Statement implements TypedefProvider { public get range() { return this.cacheRange(); } - private _range: Range; + private _range: Range | undefined; public cacheRange() { if (!this._range) { @@ -1180,12 +1179,12 @@ export class NamespaceStatement extends Statement implements TypedefProvider { return this.body.transpile(state); } - getTypedef(state: BrsTranspileState) { + getTypedef(state: BrsTranspileState): TranspileResult { let result = [ 'namespace ', ...this.getName(ParseMode.BrighterScript), state.newline - ]; + ] as TranspileResult; state.blockDepth++; result.push( ...this.body.getTypedef(state) @@ -1213,7 +1212,7 @@ export class NamespaceStatement extends Statement implements TypedefProvider { export class ImportStatement extends Statement implements TypedefProvider { constructor( readonly importToken: Token, - readonly filePathToken: Token + readonly filePathToken: Token | undefined ) { super(); this.range = util.createBoundingRange( @@ -1232,8 +1231,8 @@ export class ImportStatement extends Statement implements TypedefProvider { ); } } - public filePath: string; - public range: Range; + public filePath: string | undefined; + public range: Range | undefined; transpile(state: BrsTranspileState) { //The xml files are responsible for adding the additional script imports, but @@ -1242,7 +1241,7 @@ export class ImportStatement extends Statement implements TypedefProvider { `'`, state.transpileToken(this.importToken), ' ', - state.transpileToken(this.filePathToken) + state.transpileToken(this.filePathToken!) ]; } @@ -1254,7 +1253,7 @@ export class ImportStatement extends Statement implements TypedefProvider { this.importToken.text, ' ', //replace any `.bs` extension with `.brs` - this.filePathToken.text.replace(/\.bs"?$/i, '.brs"') + this.filePathToken!.text.replace(/\.bs"?$/i, '.brs"') ]; } @@ -1294,7 +1293,7 @@ export class InterfaceStatement extends Statement implements TypedefProvider { endInterface: Token; }; - public range: Range; + public range: Range | undefined; /** * Get the name of the wrapping namespace (if it exists) @@ -1443,7 +1442,7 @@ export class InterfaceFieldStatement extends Statement implements TypedefProvide ); } - public range: Range; + public range: Range | undefined; public tokens = {} as { optional?: Token; @@ -1464,7 +1463,7 @@ export class InterfaceFieldStatement extends Statement implements TypedefProvide //nothing to walk } - getTypedef(state: BrsTranspileState): (string | SourceNode)[] { + getTypedef(state: BrsTranspileState): TranspileResult { const result = [] as TranspileResult; for (let annotation of this.annotations ?? []) { result.push( @@ -1475,7 +1474,7 @@ export class InterfaceFieldStatement extends Statement implements TypedefProvide } if (this.isOptional) { result.push( - this.tokens.optional.text, + this.tokens.optional!.text, ' ' ); } @@ -1537,8 +1536,8 @@ export class InterfaceMethodStatement extends Statement implements TypedefProvid name: Identifier; leftParen: Token; rightParen: Token; - as: Token; - returnType: Token; + as: Token | undefined; + returnType: Token | undefined; }; public get isOptional() { @@ -1561,7 +1560,7 @@ export class InterfaceMethodStatement extends Statement implements TypedefProvid if (this.isOptional) { result.push( - this.tokens.optional.text, + this.tokens.optional!.text, ' ' ); } @@ -1579,20 +1578,22 @@ export class InterfaceMethodStatement extends Statement implements TypedefProvid } const param = params[i]; result.push(param.name.text); - if (param.typeToken?.text?.length > 0) { + const typeToken = param.typeToken; + if (typeToken && typeToken.text.length > 0) { result.push( ' as ', - param.typeToken.text + typeToken.text ); } } result.push( ')' ); - if (this.tokens.returnType?.text.length > 0) { + const returnTypeToken = this.tokens.returnType; + if (returnTypeToken && returnTypeToken.text.length > 0) { result.push( ' as ', - this.tokens.returnType.text + returnTypeToken.text ); } return result; @@ -1621,7 +1622,7 @@ export class ClassStatement extends Statement implements TypedefProvider { this.memberMap[statement?.name?.text.toLowerCase()] = statement; } else if (isFieldStatement(statement)) { this.fields.push(statement); - this.memberMap[statement?.name?.text.toLowerCase()] = statement; + this.memberMap[statement.name?.text.toLowerCase()] = statement; } } @@ -1665,10 +1666,10 @@ export class ClassStatement extends Statement implements TypedefProvider { public methods = [] as MethodStatement[]; public fields = [] as FieldStatement[]; - public readonly range: Range; + public readonly range: Range | undefined; transpile(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; //make the builder result.push(...this.getTranspiledBuilder(state)); result.push( @@ -1826,8 +1827,8 @@ export class ClassStatement extends Statement implements TypedefProvider { * without instantiating the parent constructor at that point in time. */ private getTranspiledBuilder(state: BrsTranspileState) { - let result = []; - result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript))}()\n`); + let result = [] as TranspileResult; + result.push(`function ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`); state.blockDepth++; //indent result.push(state.indent()); @@ -1841,7 +1842,7 @@ export class ClassStatement extends Statement implements TypedefProvider { if (ancestors[0]) { const ancestorNamespace = ancestors[0].findAncestor(isNamespaceStatement); let fullyQualifiedClassName = util.getFullyQualifiedClassName( - ancestors[0].getName(ParseMode.BrighterScript), + ancestors[0].getName(ParseMode.BrighterScript)!, ancestorNamespace?.getName(ParseMode.BrighterScript) ); result.push( @@ -1922,14 +1923,14 @@ export class ClassStatement extends Statement implements TypedefProvider { * This invokes the builder, gets an instance of the class, then invokes the "new" function on that class. */ private getTranspiledClassFunction(state: BrsTranspileState) { - let result = []; + let result = [] as TranspileResult; const constructorFunction = this.getConstructorFunction(); const constructorParams = constructorFunction ? constructorFunction.func.parameters : []; result.push( state.sourceNode(this.classKeyword, 'function'), state.sourceNode(this.classKeyword, ' '), - state.sourceNode(this.name, this.getName(ParseMode.BrightScript)), + state.sourceNode(this.name, this.getName(ParseMode.BrightScript)!), `(` ); let i = 0; @@ -1949,7 +1950,7 @@ export class ClassStatement extends Statement implements TypedefProvider { state.blockDepth++; result.push(state.indent()); - result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript))}()\n`); + result.push(`instance = ${this.getBuilderName(this.getName(ParseMode.BrightScript)!)}()\n`); result.push(state.indent()); result.push(`instance.new(`); @@ -2022,7 +2023,7 @@ export class MethodStatement extends FunctionStatement { return this.modifiers.find(x => accessModifiers.includes(x.kind)); } - public readonly range: Range; + public readonly range: Range | undefined; /** * Get the name of this method. @@ -2064,7 +2065,7 @@ export class MethodStatement extends FunctionStatement { } getTypedef(state: BrsTranspileState) { - const result = [] as Array; + const result = [] as TranspileResult; for (let annotation of this.annotations ?? []) { result.push( ...annotation.getTypedef(state), @@ -2093,7 +2094,7 @@ export class MethodStatement extends FunctionStatement { */ private ensureSuperConstructorCall(state: BrsTranspileState) { //if this class doesn't extend another class, quit here - if (state.classStatement.getAncestors(state).length === 0) { + if (state.classStatement!.getAncestors(state).length === 0) { return; } @@ -2103,7 +2104,7 @@ export class MethodStatement extends FunctionStatement { //is a call statement return isExpressionStatement(x) && isCallExpression(x.expression) && //is a call to super - util.findBeginningVariableExpression(x.expression.callee as any).name.text.toLowerCase() === 'super'; + util.findBeginningVariableExpression(x.expression.callee as any)?.name.text.toLowerCase() === 'super'; }) !== -1; //if a call to super exists, quit here @@ -2119,7 +2120,7 @@ export class MethodStatement extends FunctionStatement { kind: TokenKind.Identifier, text: 'super', isReserved: false, - range: state.classStatement.name.range, + range: state.classStatement!.name.range, leadingWhitespace: '' } ), @@ -2127,14 +2128,14 @@ export class MethodStatement extends FunctionStatement { kind: TokenKind.LeftParen, text: '(', isReserved: false, - range: state.classStatement.name.range, + range: state.classStatement!.name.range, leadingWhitespace: '' }, { kind: TokenKind.RightParen, text: ')', isReserved: false, - range: state.classStatement.name.range, + range: state.classStatement!.name.range, leadingWhitespace: '' }, [] @@ -2147,13 +2148,13 @@ export class MethodStatement extends FunctionStatement { * Inject field initializers at the top of the `new` function (after any present `super()` call) */ private injectFieldInitializersForConstructor(state: BrsTranspileState) { - let startingIndex = state.classStatement.hasParentClass() ? 1 : 0; + let startingIndex = state.classStatement!.hasParentClass() ? 1 : 0; let newStatements = [] as Statement[]; //insert the field initializers in order - for (let field of state.classStatement.fields) { + for (let field of state.classStatement!.fields) { let thisQualifiedName = { ...field.name }; - thisQualifiedName.text = 'm.' + field.name.text; + thisQualifiedName.text = 'm.' + field.name?.text; if (field.initialValue) { newStatements.push( new AssignmentStatement(field.equal, thisQualifiedName, field.initialValue) @@ -2162,9 +2163,9 @@ export class MethodStatement extends FunctionStatement { //if there is no initial value, set the initial value to `invalid` newStatements.push( new AssignmentStatement( - createToken(TokenKind.Equal, '=', field.name.range), + createToken(TokenKind.Equal, '=', field.name?.range), thisQualifiedName, - createInvalidLiteral('invalid', field.name.range) + createInvalidLiteral('invalid', field.name?.range) ) ); } @@ -2219,7 +2220,7 @@ export class FieldStatement extends Statement implements TypedefProvider { } } - public readonly range: Range; + public readonly range: Range | undefined; public get isOptional() { return !!this.optional; @@ -2230,7 +2231,7 @@ export class FieldStatement extends Statement implements TypedefProvider { } getTypedef(state: BrsTranspileState) { - const result = []; + const result = [] as TranspileResult; if (this.name) { for (let annotation of this.annotations ?? []) { result.push( @@ -2250,7 +2251,7 @@ export class FieldStatement extends Statement implements TypedefProvider { ' ' ); if (this.isOptional) { - result.push(this.optional.text, ' '); + result.push(this.optional!.text, ' '); } result.push(this.name?.text, ' as ', @@ -2296,19 +2297,19 @@ export class TryCatchStatement extends Statement { ); } - public readonly range: Range; + public readonly range: Range | undefined; public transpile(state: BrsTranspileState): TranspileResult { return [ state.transpileToken(this.tokens.try), - ...this.tryBranch.transpile(state), + ...this.tryBranch!.transpile(state), state.newline, state.indent(), ...(this.catchStatement?.transpile(state) ?? ['catch']), state.newline, state.indent(), - state.transpileToken(this.tokens.endTry) - ]; + state.transpileToken(this.tokens.endTry!) + ] as TranspileResult; } public walk(visitor: WalkVisitor, options: WalkOptions) { @@ -2335,7 +2336,7 @@ export class CatchStatement extends Statement { ); } - public range: Range; + public range: Range | undefined; public transpile(state: BrsTranspileState): TranspileResult { return [ @@ -2364,13 +2365,13 @@ export class ThrowStatement extends Statement { expression ); } - public range: Range; + public range: Range | undefined; public transpile(state: BrsTranspileState) { const result = [ state.transpileToken(this.throwToken), ' ' - ]; + ] as TranspileResult; //if we have an expression, transpile it if (this.expression) { @@ -2407,7 +2408,7 @@ export class EnumStatement extends Statement implements TypedefProvider { this.body = this.body ?? []; } - public get range(): Range { + public get range(): Range | undefined { return util.createBoundingRange( this.tokens.enum, this.tokens.name, @@ -2585,7 +2586,7 @@ export class EnumMemberStatement extends Statement implements TypedefProvider { return []; } - getTypedef(state: BrsTranspileState): (string | SourceNode)[] { + getTypedef(state: BrsTranspileState): TranspileResult { const result = [ this.tokens.name.text ] as TranspileResult; @@ -2621,7 +2622,7 @@ export class ConstStatement extends Statement implements TypedefProvider { this.range = util.createBoundingRange(this.tokens.const, this.tokens.name, this.tokens.equals, this.value); } - public range: Range; + public range: Range | undefined; public get name() { return this.tokens.name.text; @@ -2651,7 +2652,7 @@ export class ConstStatement extends Statement implements TypedefProvider { return []; } - getTypedef(state: BrsTranspileState): (string | SourceNode)[] { + getTypedef(state: BrsTranspileState): TranspileResult { return [ state.tokenToSourceNode(this.tokens.const), ' ', @@ -2684,7 +2685,7 @@ export class ContinueStatement extends Statement { ); } - public range: Range; + public range: Range | undefined; transpile(state: BrsTranspileState) { return [ diff --git a/src/parser/TranspileState.ts b/src/parser/TranspileState.ts index 379791ebe..e42d8239b 100644 --- a/src/parser/TranspileState.ts +++ b/src/parser/TranspileState.ts @@ -1,6 +1,8 @@ import { SourceNode } from 'source-map'; import type { Range } from 'vscode-languageserver'; import type { BsConfig } from '../BsConfig'; +import type { TranspileResult } from '../interfaces'; +import util from '../util'; /** * Holds the state of a transpile operation as it works its way through the transpile process @@ -54,8 +56,8 @@ export class TranspileState { /** * Shorthand for creating a new source node */ - public sourceNode(locatable: { range?: Range }, code: string | SourceNode | Array): SourceNode { - return new SourceNode( + public sourceNode(locatable: { range?: Range }, code: string | SourceNode | TranspileResult): SourceNode { + return util.sourceNodeFromTranspileResult( //convert 0-based range line to 1-based SourceNode line locatable.range ? locatable.range.start.line + 1 : null, //range and SourceNode character are both 0-based, so no conversion necessary diff --git a/src/util.ts b/src/util.ts index e158b7d9f..13ee830a7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -9,7 +9,7 @@ import { URI } from 'vscode-uri'; import * as xml2js from 'xml2js'; import type { BsConfig, FinalizedBsConfig } from './BsConfig'; import { DiagnosticMessages } from './DiagnosticMessages'; -import type { CallableContainer, BsDiagnostic, FileReference, CallableContainerMap, CompilerPluginFactory, CompilerPlugin, ExpressionInfo } from './interfaces'; +import type { CallableContainer, BsDiagnostic, FileReference, CallableContainerMap, CompilerPluginFactory, CompilerPlugin, ExpressionInfo, TranspileResult } from './interfaces'; import { BooleanType } from './types/BooleanType'; import { DoubleType } from './types/DoubleType'; import { DynamicType } from './types/DynamicType'; @@ -859,8 +859,8 @@ export class Util { /** * If the two items have lines that touch */ - public linesTouch(first: { range: Range }, second: { range: Range }) { - if (first && second && ( + public linesTouch(first: { range?: Range | undefined }, second: { range?: Range | undefined }) { + if (first && second && (first.range !== undefined) && (second.range !== undefined) && ( first.range.start.line === second.range.start.line || first.range.start.line === second.range.end.line || first.range.end.line === second.range.start.line || @@ -987,11 +987,26 @@ export class Util { }; } + /** + * Create a `Range` from two potentially-undefined `Position`s + */ + public createRangeFromPositionsOptional(startPosition: Position | undefined, endPosition: Position | undefined): Range | undefined { + if (startPosition && endPosition) { + return this.createRangeFromPositions(startPosition, endPosition); + } else if (startPosition) { + return this.createRangeFromPositions(startPosition, startPosition); + } else if (endPosition) { + return this.createRangeFromPositions(endPosition, endPosition); + } + + return undefined; + } + /** * Given a list of ranges, create a range that starts with the first non-null lefthand range, and ends with the first non-null * righthand range. Returns undefined if none of the items have a range. */ - public createBoundingRange(...locatables: Array<{ range?: Range }>): Range | undefined { + public createBoundingRange(...locatables: Array<{ range?: Range } | null | undefined>): Range | undefined { let leftmostRange: Range | undefined; let rightmostRange: Range | undefined; @@ -1527,6 +1542,21 @@ export class Util { }]); } } + + /** + * Wraps SourceNode's constructor to be compatible with the TranspileResult type + */ + public sourceNodeFromTranspileResult( + line: number | null, + column: number | null, + source: string | null, + chunks?: string | SourceNode | TranspileResult, + name?: string + ): SourceNode { + // we can use a typecast rather than actually transforming the data because SourceNode + // accepts a more permissive type than its typedef states + return new SourceNode(line, column, source, chunks as any, name); + } } /** From d2338c2a0a17752195eb0692c97d76382cfe579f Mon Sep 17 00:00:00 2001 From: Joseph Junker Date: Mon, 4 Mar 2024 07:44:06 -0800 Subject: [PATCH 2/2] updates per CR feedback --- src/parser/Expression.ts | 4 +++- src/util.ts | 15 --------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/parser/Expression.ts b/src/parser/Expression.ts index 2308b78d3..cc55bd926 100644 --- a/src/parser/Expression.ts +++ b/src/parser/Expression.ts @@ -29,7 +29,9 @@ export class BinaryExpression extends Expression { public right: Expression ) { super(); - this.range = util.createRangeFromPositionsOptional(this.left.range?.start, this.right.range?.end); + this.range = this.left.range && this.right.range + ? util.createRangeFromPositions(this.left.range.start, this.right.range.end) + : undefined; } public readonly range: Range | undefined; diff --git a/src/util.ts b/src/util.ts index 6259e5d63..a49f9f202 100644 --- a/src/util.ts +++ b/src/util.ts @@ -988,21 +988,6 @@ export class Util { }; } - /** - * Create a `Range` from two potentially-undefined `Position`s - */ - public createRangeFromPositionsOptional(startPosition: Position | undefined, endPosition: Position | undefined): Range | undefined { - if (startPosition && endPosition) { - return this.createRangeFromPositions(startPosition, endPosition); - } else if (startPosition) { - return this.createRangeFromPositions(startPosition, startPosition); - } else if (endPosition) { - return this.createRangeFromPositions(endPosition, endPosition); - } - - return undefined; - } - /** * Given a list of ranges, create a range that starts with the first non-null lefthand range, and ends with the first non-null * righthand range. Returns undefined if none of the items have a range.