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:
Matiss Janis Aboltins
2026-02-04 19:29:19 +00:00
committed by GitHub
parent 0d6742664b
commit a0a490c14c
3 changed files with 53 additions and 16 deletions

View File

@@ -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 });

View File

@@ -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;
}

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MatissJanis]
---
typescript: port arithmetic.ts to strict TS