From aca7ee3df1f2901e02c2cba6536751eb0113295e Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Thu, 16 Mar 2023 18:08:51 -0400 Subject: [PATCH] Lint the expressions package --- expressions/src/ast.ts | 2 +- expressions/src/completion.ts | 3 +++ expressions/src/evaluator.test.ts | 6 +++++ expressions/src/evaluator.ts | 14 +++++++----- expressions/src/idxHelper.ts | 9 +++++--- expressions/src/lexer.ts | 1 + expressions/src/parser.ts | 21 +++++++++--------- expressions/src/result.ts | 37 +++++++++++++++++++------------ expressions/src/xlang.test.ts | 22 +++++++++--------- 9 files changed, 70 insertions(+), 45 deletions(-) diff --git a/expressions/src/ast.ts b/expressions/src/ast.ts index cb37fad..51bfedc 100644 --- a/expressions/src/ast.ts +++ b/expressions/src/ast.ts @@ -97,7 +97,7 @@ export class IndexAccess extends Expr { } export class Star extends Expr { - accept(v: ExprVisitor): R { + accept(): R { throw new Error("Method not implemented."); } } diff --git a/expressions/src/completion.ts b/expressions/src/completion.ts index 7a29a60..1708905 100644 --- a/expressions/src/completion.ts +++ b/expressions/src/completion.ts @@ -81,6 +81,9 @@ export function complete( extensionFunctions ); const expr = p.parse(); + if (!expr) { + return []; + } const ev = new Evaluator(expr, context, functions); const result = ev.evaluate(); diff --git a/expressions/src/evaluator.test.ts b/expressions/src/evaluator.test.ts index e1fa8fd..e63ceeb 100644 --- a/expressions/src/evaluator.test.ts +++ b/expressions/src/evaluator.test.ts @@ -1,9 +1,14 @@ +import {Expr} from "./ast"; import * as data from "./data"; import {ExpressionEvaluationError} from "./errors"; import {Evaluator} from "./evaluator"; import {Lexer} from "./lexer"; import {Parser} from "./parser"; +function assertDefined(x: Expr | undefined): asserts x is Expr { + expect(x).toBeDefined(); +} + describe("evaluator", () => { const lexAndParse = (input: string) => { const lexer = new Lexer(input); @@ -12,6 +17,7 @@ describe("evaluator", () => { // Parse const parser = new Parser(result.tokens, ["foo"], []); const expr = parser.parse(); + assertDefined(expr); return expr; }; diff --git a/expressions/src/evaluator.ts b/expressions/src/evaluator.ts index 54c27e6..8f5964b 100644 --- a/expressions/src/evaluator.ts +++ b/expressions/src/evaluator.ts @@ -93,6 +93,7 @@ export class Evaluator implements ExprVisitor { } // result is always assigned before we return here + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return result!; } @@ -101,6 +102,7 @@ export class Evaluator implements ExprVisitor { } visitContextAccess(contextAccess: ContextAccess): data.ExpressionData { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const r = this.context.get(contextAccess.name.lexeme)!; return r; } @@ -114,7 +116,7 @@ export class Evaluator implements ExprVisitor { try { idxResult = this.eval(ia.index); } catch (e) { - throw new Error(`could not evaluate index for index access: ${e}`, {cause: e}); + throw new Error(`could not evaluate index for index access: ${(e as Error).message}`, {cause: e}); } idx = new idxHelper(false, idxResult); } @@ -124,9 +126,9 @@ export class Evaluator implements ExprVisitor { let result: data.ExpressionData; switch (objResult.kind) { case data.Kind.Array: { - const tobjResult = objResult as data.Array; + const tobjResult = objResult; if (tobjResult instanceof FilteredArray) { - result = filteredArrayAccess(tobjResult as FilteredArray, idx); + result = filteredArrayAccess(tobjResult, idx); } else { result = arrayAccess(tobjResult, idx); } @@ -135,7 +137,7 @@ export class Evaluator implements ExprVisitor { } case data.Kind.Dictionary: { - const tobjResult = objResult as data.Dictionary; + const tobjResult = objResult; result = objectAccess(tobjResult, idx); break; } @@ -170,7 +172,7 @@ function filteredArrayAccess(fa: FilteredArray, idx: idxHelper): data.Expression // Check the type of the nested item switch (item.kind) { case data.Kind.Dictionary: { - const ti = item as data.Dictionary; + const ti = item; if (idx.star) { for (const v of ti.values()) { result.add(v); @@ -186,7 +188,7 @@ function filteredArrayAccess(fa: FilteredArray, idx: idxHelper): data.Expression } case data.Kind.Array: { - const ti = item as data.Array; + const ti = item; if (idx.star) { for (const v of ti.values()) { result.add(v); diff --git a/expressions/src/idxHelper.ts b/expressions/src/idxHelper.ts index a92fb9a..e79e73f 100644 --- a/expressions/src/idxHelper.ts +++ b/expressions/src/idxHelper.ts @@ -5,12 +5,15 @@ export class idxHelper { public readonly int: number | undefined; constructor(public readonly star: boolean, idx: ExpressionData | undefined) { + if (!idx) { + return; + } if (!star) { - if (idx!.primitive) { - this.str = idx!.coerceString(); + if (idx.primitive) { + this.str = idx.coerceString(); } - let f = idx!.number(); + let f = idx.number(); if (!isNaN(f) && isFinite(f) && f >= 0) { f = Math.floor(f); this.int = f; diff --git a/expressions/src/lexer.ts b/expressions/src/lexer.ts index c52bbf2..b9fd59b 100644 --- a/expressions/src/lexer.ts +++ b/expressions/src/lexer.ts @@ -57,6 +57,7 @@ export function tokenString(tok: Token): string { case TokenType.NUMBER: return tok.lexeme; case TokenType.STRING: + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return tok.value!.toString(); default: return tok.lexeme; diff --git a/expressions/src/parser.ts b/expressions/src/parser.ts index 9345bd6..9c15c23 100644 --- a/expressions/src/parser.ts +++ b/expressions/src/parser.ts @@ -9,8 +9,8 @@ export class Parser { private extContexts: Map; private extFuncs: Map; - private offset: number = 0; - private depth: number = 0; + private offset = 0; + private depth = 0; private context: ParseContext; @@ -43,15 +43,13 @@ export class Parser { }; } - public parse(): Expr { - let result!: Expr; - + public parse(): Expr | undefined { // No tokens if (this.atEnd()) { - return result; + return; } - result = this.expression(); + const result = this.expression(); if (!this.atEnd()) { throw this.buildError(ErrorType.ErrorUnexpectedSymbol, this.peek()); @@ -173,7 +171,7 @@ export class Parser { let cont = true; while (cont) { switch (true) { - case this.match(TokenType.LEFT_BRACKET): + case this.match(TokenType.LEFT_BRACKET): { let indexExpr: Expr; if (this.match(TokenType.STAR)) { indexExpr = new Star(); @@ -188,6 +186,7 @@ export class Parser { depthIncreased++; expr = new IndexAccess(expr, indexExpr); break; + } case this.match(TokenType.DOT): // Track depth @@ -195,7 +194,7 @@ export class Parser { depthIncreased++; if (this.match(TokenType.IDENTIFIER)) { - let property = this.previous(); + const property = this.previous(); expr = new IndexAccess(expr, new Literal(new data.StringData(property.lexeme), property)); } else if (this.match(TokenType.STAR)) { expr = new IndexAccess(expr, new Star()); @@ -268,7 +267,7 @@ export class Parser { case this.match(TokenType.STRING): return new Literal(new data.StringData(this.previous().value as string), this.previous()); - case this.match(TokenType.LEFT_PAREN): + case this.match(TokenType.LEFT_PAREN): { const expr = this.expression(); if (this.atEnd()) { @@ -277,7 +276,7 @@ export class Parser { this.consume(TokenType.RIGHT_PAREN, ErrorType.ErrorUnexpectedSymbol); return new Grouping(expr); - + } case this.atEnd(): throw this.buildError(ErrorType.ErrorUnexpectedEndOfExpression, this.previous()); // Back up to get the last token before the EOF } diff --git a/expressions/src/result.ts b/expressions/src/result.ts index 4c5d1c7..64b461e 100644 --- a/expressions/src/result.ts +++ b/expressions/src/result.ts @@ -8,10 +8,10 @@ export function falsy(d: data.ExpressionData): boolean { case data.Kind.Boolean: return !d.value; - case data.Kind.Number: + case data.Kind.Number: { const dv = d.value; return dv === 0 || isNaN(dv); - + } case data.Kind.String: return d.value.length === 0; } @@ -77,7 +77,7 @@ export function coerceTypes( // Similar to the Javascript abstract equality comparison algorithm http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3. // Except string comparison is OrdinalIgnoreCase, and objects are not coerced to primitives. export function equals(lhs: data.ExpressionData, rhs: data.ExpressionData): boolean { - let [lv, rv] = coerceTypes(lhs, rhs); + const [lv, rv] = coerceTypes(lhs, rhs); if (lv.kind != rv.kind) { return false; @@ -89,7 +89,7 @@ export function equals(lhs: data.ExpressionData, rhs: data.ExpressionData): bool return true; // Number, Number - case data.Kind.Number: + case data.Kind.Number: { const ld = lv.value; const rd = (rv as data.NumberData).value; if (isNaN(ld) || isNaN(rd)) { @@ -97,18 +97,21 @@ export function equals(lhs: data.ExpressionData, rhs: data.ExpressionData): bool } return ld == rd; + } // String, String - case data.Kind.String: + case data.Kind.String: { const ls = lv.value; const rs = (rv as data.StringData).value; return toUpperSpecial(ls) === toUpperSpecial(rs); + } // Boolean, Boolean - case data.Kind.Boolean: + case data.Kind.Boolean: { const lb = lv.value; const rb = (rv as data.BooleanData).value; return lb == rb; + } // Object, Object case data.Kind.Dictionary: @@ -131,7 +134,7 @@ export function greaterThan(lhs: data.ExpressionData, rhs: data.ExpressionData): switch (lv.kind) { // Number, Number - case data.Kind.Number: + case data.Kind.Number: { const lf = lv.value; const rf = (rv as data.NumberData).value; if (isNaN(lf) || isNaN(rf)) { @@ -139,20 +142,23 @@ export function greaterThan(lhs: data.ExpressionData, rhs: data.ExpressionData): } return lf > rf; + } // String, String - case data.Kind.String: + case data.Kind.String: { let ls = lv.value; let rs = (rv as data.StringData).value; ls = toUpperSpecial(ls); rs = toUpperSpecial(rs); return ls > rs; + } // Boolean, Boolean - case data.Kind.Boolean: - const lb = (lv as data.BooleanData).value; + case data.Kind.Boolean: { + const lb = lv.value; const rb = (rv as data.BooleanData).value; return lb && !rb; + } } return false; @@ -169,7 +175,7 @@ export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): bo switch (lv.kind) { // Number, Number - case data.Kind.Number: + case data.Kind.Number: { const lf = lv.value; const rf = (rv as data.NumberData).value; if (isNaN(lf) || isNaN(rf)) { @@ -177,20 +183,23 @@ export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): bo } return lf < rf; + } // String, String - case data.Kind.String: + case data.Kind.String: { let ls = lv.value; let rs = (rv as data.StringData).value; ls = toUpperSpecial(ls); rs = toUpperSpecial(rs); return ls < rs; + } // Boolean, Boolean - case data.Kind.Boolean: + case data.Kind.Boolean: { const lb = lv.value; const rb = (rv as data.BooleanData).value; return !lb && rb; + } } return false; @@ -200,7 +209,7 @@ export function lessThan(lhs: data.ExpressionData, rhs: data.ExpressionData): bo export function toUpperSpecial(s: string): string { const sb: string[] = []; let i = 0; - let len = s.length; + const len = s.length; let found = s.indexOf("ı"); while (i < len) { if (i < found) { diff --git a/expressions/src/xlang.test.ts b/expressions/src/xlang.test.ts index 4d67be7..941335b 100644 --- a/expressions/src/xlang.test.ts +++ b/expressions/src/xlang.test.ts @@ -42,7 +42,7 @@ interface TestCase { options: TestOptions; } -function testCaseReviver(key: string, val: any): any { +function testCaseReviver(key: string, val: unknown): unknown { if (key === "contexts") { const tmp = JSON.stringify(val); return JSON.parse(tmp, reviver); @@ -105,27 +105,28 @@ describe("x-lang tests", () => { if (testCase.err && testCase.err.kind === TestErrorKind.Lexing) { throw new Error("expected error lexing expression, but got none"); } - } catch (e: any) { + } catch (e) { + const err = e as Error; // Did test expect lexing error? If so, compare error message. if (testCase.err && testCase.err.kind === TestErrorKind.Lexing) { - expect(e.message).toContain(testCase.err.value); + expect(err.message).toContain(testCase.err.value); return; } - throw new Error(`unexpected error lexing expression: ${e.message} ${e.stack}`); + throw new Error(`unexpected error lexing expression: ${err.message} ${err.stack || ""}`); } // Parse const contextNames = testCase.contexts.pairs().map(x => x.key); const parser = new Parser(result.tokens, contextNames, []); - let expr: Expr; + let expr: Expr | undefined; try { expr = parser.parse(); if (testCase.err?.kind === TestErrorKind.Parsing) { throw new Error("expected error parsing expression, but got none"); } - } catch (e: any) { + } catch (e) { // Did test expect parsing error? if (testCase.err?.kind === TestErrorKind.Parsing) { // Test expects parsing error @@ -133,8 +134,8 @@ describe("x-lang tests", () => { expect(errorWithExpression(pe, testCase.expr)).toContain(testCase.err.value); return; } - - throw new Error(`unexpected error parsing expression: ${e.message} ${e.stack}`); + const err = e as Error; + throw new Error(`unexpected error parsing expression: ${err.message} ${err.stack || ""}`); } // Evaluate expression @@ -154,14 +155,15 @@ describe("x-lang tests", () => { expect(kindStr(result.kind)).toBe(testCase.result.kind); expect(JSON.stringify(result, replacer)).toEqual(JSON.stringify(testCase.result.value, replacer)); - } catch (e: any) { + } catch (e) { if (testCase.err?.kind === TestErrorKind.Evaluation) { const pe = e as ExpressionError; expect(errorWithExpression(pe, testCase.expr)).toContain(testCase.err.value); return; } - throw new Error(`unexpected error evaluating expression: ${e.message} ${e.stack}`); + const err = e as Error; + throw new Error(`unexpected error evaluating expression: ${err.message} ${err.stack || ""}`); } }); });