mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-28 10:33:02 -05:00
Typescript: make arithmetic.ts strict (#6801)
* Make arithmetic.ts strict TypeScript compliant - Add type definitions for ParserState, Operator, OperatorNode, and AstNode - Add explicit type annotations to all function parameters - Fix null/undefined handling in parsePrimary function - Remove type assertion in makeOperatorParser by using explicit Operator type - Handle null return from currencyToAmount function - All functions now have proper return type annotations * Add test for ignoring leftover characters in evalArithmetic function
This commit is contained in:
committed by
GitHub
parent
0d6742664b
commit
a0a490c14c
@@ -49,6 +49,14 @@ describe('arithmetic', () => {
|
||||
expect(evalArithmetic(`1\u2019222.45`)).toEqual(1222.45);
|
||||
});
|
||||
|
||||
test('ignores leftover characters', () => {
|
||||
expect(evalArithmetic('1+2)')).toBe(3);
|
||||
expect(evalArithmetic('1+2)foo')).toBe(3);
|
||||
expect(evalArithmetic('(1+2)x')).toBe(3);
|
||||
expect(evalArithmetic('10+20 trailing')).toBe(30);
|
||||
expect(evalArithmetic('1+2(3')).toBe(3);
|
||||
});
|
||||
|
||||
test('handles apostrophe-dot format with keyboard apostrophe (U+0027)', () => {
|
||||
setNumberFormat({ format: 'apostrophe-dot', hideFraction: false });
|
||||
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
// @ts-strict-ignore
|
||||
import { currencyToAmount } from './util';
|
||||
|
||||
function fail(state, msg) {
|
||||
type ParserState = {
|
||||
str: string;
|
||||
index: number;
|
||||
};
|
||||
|
||||
type Operator = '+' | '-' | '*' | '/' | '^';
|
||||
|
||||
type OperatorNode = {
|
||||
op: Operator;
|
||||
left: AstNode;
|
||||
right: AstNode;
|
||||
};
|
||||
|
||||
type AstNode = number | OperatorNode;
|
||||
|
||||
function fail(state: ParserState, msg: string): never {
|
||||
throw new Error(
|
||||
msg + ': ' + JSON.stringify(state.str.slice(state.index, 10)),
|
||||
);
|
||||
}
|
||||
|
||||
function char(state) {
|
||||
function char(state: ParserState): string | undefined {
|
||||
return state.str[state.index];
|
||||
}
|
||||
|
||||
function next(state) {
|
||||
function next(state: ParserState): string | null {
|
||||
if (state.index >= state.str.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ch = char(state);
|
||||
state.index++;
|
||||
return ch;
|
||||
return ch ?? null;
|
||||
}
|
||||
|
||||
function nextOperator(state, op) {
|
||||
function nextOperator(state: ParserState, op: string): boolean {
|
||||
if (char(state) === op) {
|
||||
next(state);
|
||||
return true;
|
||||
@@ -30,7 +44,7 @@ function nextOperator(state, op) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function parsePrimary(state) {
|
||||
function parsePrimary(state: ParserState): number {
|
||||
// We only support numbers
|
||||
const isNegative = char(state) === '-';
|
||||
if (isNegative) {
|
||||
@@ -38,11 +52,16 @@ function parsePrimary(state) {
|
||||
}
|
||||
|
||||
let numberStr = '';
|
||||
let currentChar = char(state);
|
||||
while (
|
||||
char(state) &&
|
||||
char(state).match(/[0-9,.'\u2019\u00A0\u202F ]|\p{Sc}/u)
|
||||
currentChar &&
|
||||
currentChar.match(/[0-9,.'\u2019\u00A0\u202F ]|\p{Sc}/u)
|
||||
) {
|
||||
numberStr += next(state);
|
||||
const ch = next(state);
|
||||
if (ch !== null) {
|
||||
numberStr += ch;
|
||||
}
|
||||
currentChar = char(state);
|
||||
}
|
||||
|
||||
if (numberStr === '') {
|
||||
@@ -50,10 +69,13 @@ function parsePrimary(state) {
|
||||
}
|
||||
|
||||
const number = currencyToAmount(numberStr);
|
||||
if (number === null) {
|
||||
fail(state, 'Invalid number format');
|
||||
}
|
||||
return isNegative ? -number : number;
|
||||
}
|
||||
|
||||
function parseParens(state) {
|
||||
function parseParens(state: ParserState): AstNode {
|
||||
if (char(state) === '(') {
|
||||
next(state);
|
||||
const expr = parseOperator(state);
|
||||
@@ -69,9 +91,10 @@ function parseParens(state) {
|
||||
return parsePrimary(state);
|
||||
}
|
||||
|
||||
function makeOperatorParser(...ops) {
|
||||
return ops.reduce((prevParser, op) => {
|
||||
return state => {
|
||||
function makeOperatorParser<T extends readonly Operator[]>(...ops: T) {
|
||||
type ParserFn = (state: ParserState) => AstNode;
|
||||
return ops.reduce<ParserFn>((prevParser: ParserFn, op: Operator) => {
|
||||
return (state: ParserState) => {
|
||||
let node = prevParser(state);
|
||||
while (nextOperator(state, op)) {
|
||||
node = { op, left: node, right: prevParser(state) };
|
||||
@@ -84,12 +107,12 @@ function makeOperatorParser(...ops) {
|
||||
// These operators go from high to low order of precedence
|
||||
const parseOperator = makeOperatorParser('^', '/', '*', '-', '+');
|
||||
|
||||
function parse(expression: string) {
|
||||
function parse(expression: string): AstNode {
|
||||
const state = { str: expression.replace(/\s/g, ''), index: 0 };
|
||||
return parseOperator(state);
|
||||
}
|
||||
|
||||
function evaluate(ast): number {
|
||||
function evaluate(ast: AstNode): number {
|
||||
if (typeof ast === 'number') {
|
||||
return ast;
|
||||
}
|
||||
|
||||
6
upcoming-release-notes/6801.md
Normal file
6
upcoming-release-notes/6801.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MatissJanis]
|
||||
---
|
||||
|
||||
typescript: port arithmetic.ts to strict TS
|
||||
Reference in New Issue
Block a user