diff --git a/CHANGELOG.md b/CHANGELOG.md index 323c746b2..376ecfa7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added a message after `npm i hyperformula` that points AI assistants to the SKILLS file. The message needs `--foreground-scripts` to be visible in npm, and pnpm 10 needs the `postinstall` script to be approved first. [#1829](https://github.com/handsontable/hyperformula/pull/1829) +### Changed + +- Changed the cell errors produced while evaluating a formula to carry a message describing their cause. [#1547](https://github.com/handsontable/hyperformula/issues/1547) + ### Fixed - Fixed the `EDATE` function returning the 28th of February instead of the 29th when the shifted date falls in February of a leap year (e.g. `=EDATE(DATE(2028, 1, 31), 1)` now returns `2028-02-29` instead of `2028-02-28`). [#1781](https://github.com/handsontable/hyperformula/pull/1781) diff --git a/hyperformula/.eslintrc.js b/hyperformula/.eslintrc.js index b5f861da4..cd8f06cd2 100644 --- a/hyperformula/.eslintrc.js +++ b/hyperformula/.eslintrc.js @@ -145,6 +145,25 @@ module.exports = { 'sort-keys': ['error', 'asc'], } }, + { + files: ['**/src/interpreter/**/*.ts'], + rules: { + // An engine-thrown cell error must always state its cause. + // The public `CellError` constructor keeps `message` optional for custom + // functions (docs/guide/custom-functions.md); this rule enforces the + // stricter contract on our own code only. + 'no-restricted-syntax': ['error', + { + selector: "NewExpression[callee.name='CellError'][arguments.length<2]", + message: 'Pass an ErrorMessage constant as the second argument — no cell error may be thrown without a cause.', + }, + { + selector: "NewExpression[callee.name='CellError'] > Identifier.arguments:nth-child(2)[name='undefined']", + message: 'An explicit undefined message is not allowed — pass an ErrorMessage constant.', + }, + ], + }, + }, { files: ['**/*.spec.ts'], rules: { diff --git a/hyperformula/src/error-message.ts b/hyperformula/src/error-message.ts index d42040901..fbc530ac4 100644 --- a/hyperformula/src/error-message.ts +++ b/hyperformula/src/error-message.ts @@ -12,6 +12,7 @@ export class ErrorMessage { public static EmptyArg = 'Empty function argument.' public static EmptyArray = 'Empty array not allowed.' public static ArrayDimensions = 'Array dimensions are not compatible.' + public static NoBroadcastValue = 'Operand ranges cannot be broadcast to a common size, so this position has no value.' public static PoolDimensions = 'Range dimensions are not compatible with the window size and the stride.' public static NoSpaceForArrayResult = 'No space for array result.' public static ValueSmall = 'Value too small.' @@ -20,6 +21,7 @@ export class ErrorMessage { public static RangeManySheets = 'Range spans more than one sheet.' public static CellRangeExpected = 'Cell range expected.' public static WrongDimension = 'Wrong range dimension.' + public static SearchRangeDimension = 'Search range needs to be a single row or a single column, not a two-dimensional range.' public static ScalarExpected = 'Cell range not allowed.' public static NumberCoercion = 'Value cannot be coerced to number.' public static NumberExpected = 'Number argument expected.' @@ -40,6 +42,7 @@ export class ErrorMessage { public static BadRef = 'Address is not correct.' public static NumberRange = 'Number-only range expected.' public static ValueNotFound = 'Value not found.' + public static NaFunction = 'The NA function always evaluates to the #N/A error.' public static ValueBaseLarge = 'Value in base too large.' public static ValueBaseSmall = 'Value in base too small.' public static ValueBaseLong = 'Value in base too long.' @@ -48,6 +51,8 @@ export class ErrorMessage { public static OneValue = 'Needs at least one value.' public static TwoValues = 'Range needs to contain at least two elements.' public static ThreeValues = 'Range needs to contain at least three elements.' + public static TwoCashFlows = 'A schedule of cash flows needs at least two entries to define a rate of return.' + public static PositiveAndNegative = 'Needs at least one positive and one negative value.' public static EqualXValues = 'All x values are equal.' public static IndexBounds = 'Index out of bounds.' public static IndexLarge = 'Index too large.' @@ -56,6 +61,7 @@ export class ErrorMessage { public static ParseError = 'Parsing error.' public static SheetRef = 'Sheet does not exist.' public static PeriodLong = 'Period number cannot exceed life length.' + public static NonZeroPeriods = 'Number of periods cannot be 0 when the rate is -1.' public static InvalidDate = 'Invalid date.' public static BitshiftLong = 'Result of bitshift is too long.' public static EmptyString = 'Empty-string argument not allowed.' @@ -63,11 +69,15 @@ export class ErrorMessage { public static NegativeTime = 'Time cannot be negative.' public static NoDefault = 'No default option.' public static NoConditionMet = 'None of the conditions were met.' + public static NoConvergence = 'Calculation did not converge.' + public static RateGuess = 'The iterative rate search needs an initial guess greater than -1.' public static Selector = 'Selector cannot exceed the number of arguments.' public static StartEndDate = 'Start date needs to be earlier than end date.' + public static MaturityLong = 'Maturity date cannot be more than one year after the settlement date.' public static IncorrectDateTime = 'String does not represent correct DateTime.' public static CharacterCodeBounds = 'Character code out of bounds.' public static NonZero = 'Argument cannot be 0.' + public static DivisionByZero = 'Division by zero.' public static LessThanOne = 'Argument cannot be less than 1.' public static WeekendString = 'Incorrect weekend bitmask string.' public static InvalidRoman = 'Invalid roman numeral.' diff --git a/hyperformula/src/interpreter/ArithmeticHelper.ts b/hyperformula/src/interpreter/ArithmeticHelper.ts index 73719a60f..d393c546b 100644 --- a/hyperformula/src/interpreter/ArithmeticHelper.ts +++ b/hyperformula/src/interpreter/ArithmeticHelper.ts @@ -197,7 +197,7 @@ export class ArithmeticHelper { const left = getRawValue(leftArg) const right = getRawValue(rightArg) if (right === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } else { const typeOfResult = inferExtendedNumberTypeMultiplicative(leftArg, rightArg) return this.ExtendedNumberFactory(left / right, typeOfResult) diff --git a/hyperformula/src/interpreter/Interpreter.ts b/hyperformula/src/interpreter/Interpreter.ts index 644433253..cd8907dc5 100644 --- a/hyperformula/src/interpreter/Interpreter.ts +++ b/hyperformula/src/interpreter/Interpreter.ts @@ -467,7 +467,7 @@ export class Interpreter { if (i1 < arg1.height() && i2 < arg2.height() && j1 < arg1.width() && j2 < arg2.width()) { ret[i][j] = op(arg1.data[i1][j1], arg2.data[i2][j2]) } else { - ret[i][j] = new CellError(ErrorType.NA) + ret[i][j] = new CellError(ErrorType.NA, ErrorMessage.NoBroadcastValue) } } } diff --git a/hyperformula/src/interpreter/plugin/ComplexPlugin.ts b/hyperformula/src/interpreter/plugin/ComplexPlugin.ts index a3e0c3365..e598c9fdf 100644 --- a/hyperformula/src/interpreter/plugin/ComplexPlugin.ts +++ b/hyperformula/src/interpreter/plugin/ComplexPlugin.ts @@ -209,7 +209,7 @@ export class ComplexPlugin extends FunctionPlugin implements FunctionPluginTypec return this.runFunction(ast.args, state, this.metadata('IMARGUMENT'), ([re, im]: complex) => { if (re === 0 && im === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return Math.atan2(im, re) } diff --git a/hyperformula/src/interpreter/plugin/ConditionalAggregationPlugin.ts b/hyperformula/src/interpreter/plugin/ConditionalAggregationPlugin.ts index 774f418f6..bf29bdfaf 100644 --- a/hyperformula/src/interpreter/plugin/ConditionalAggregationPlugin.ts +++ b/hyperformula/src/interpreter/plugin/ConditionalAggregationPlugin.ts @@ -176,7 +176,7 @@ export class ConditionalAggregationPlugin extends FunctionPlugin implements Func if (averageResult instanceof CellError) { return averageResult } else { - return averageResult.averageValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return averageResult.averageValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } diff --git a/hyperformula/src/interpreter/plugin/DatabasePlugin.ts b/hyperformula/src/interpreter/plugin/DatabasePlugin.ts index b91c802b1..2f809cf59 100644 --- a/hyperformula/src/interpreter/plugin/DatabasePlugin.ts +++ b/hyperformula/src/interpreter/plugin/DatabasePlugin.ts @@ -217,7 +217,7 @@ export class DatabasePlugin extends FunctionPlugin implements FunctionPluginType } if (count === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return sum / count @@ -338,7 +338,7 @@ export class DatabasePlugin extends FunctionPlugin implements FunctionPluginType return values } - return MomentsAggregate.of(values).stdevSValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return MomentsAggregate.of(values).stdevSValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) }) } @@ -357,7 +357,7 @@ export class DatabasePlugin extends FunctionPlugin implements FunctionPluginType return values } - return MomentsAggregate.of(values).stdevPValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return MomentsAggregate.of(values).stdevPValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) }) } @@ -375,7 +375,7 @@ export class DatabasePlugin extends FunctionPlugin implements FunctionPluginType return values } - return MomentsAggregate.of(values).varSValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return MomentsAggregate.of(values).varSValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) }) } @@ -394,7 +394,7 @@ export class DatabasePlugin extends FunctionPlugin implements FunctionPluginType return values } - return MomentsAggregate.of(values).varPValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return MomentsAggregate.of(values).varPValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) }) } diff --git a/hyperformula/src/interpreter/plugin/FinancialPlugin.ts b/hyperformula/src/interpreter/plugin/FinancialPlugin.ts index c1f0c782a..106b793c7 100644 --- a/hyperformula/src/interpreter/plugin/FinancialPlugin.ts +++ b/hyperformula/src/interpreter/plugin/FinancialPlugin.ts @@ -383,7 +383,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('DDB'), (cost: number, salvage: number, life: number, period: number, factor: number) => { if (period > life) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.PeriodLong) } let rate = factor / life let oldValue @@ -407,7 +407,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('DOLLARDE'), (dollar: number, fraction: number) => { if (fraction < 1) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } fraction = Math.trunc(fraction) @@ -423,7 +423,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('DOLLARFR'), (dollar: number, fraction: number) => { if (fraction < 1) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } fraction = Math.trunc(fraction) @@ -448,7 +448,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('ISPMT'), (rate, period, periods, value) => { if (periods === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return value * rate * (period / periods - 1) } @@ -469,7 +469,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp (rate: number, payment: number, present: number, future: number, type: number) => { if (rate === 0) { if (payment === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return (-present - future) / payment } @@ -486,7 +486,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('RATE'), (periods: number, payment: number, present: number, future: number, type: number, guess: number) => { if (guess <= -1) { - return new CellError(ErrorType.VALUE) + return new CellError(ErrorType.VALUE, ErrorMessage.RateGuess) } const epsMax = 1e-7 @@ -497,7 +497,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp type = type ? 1 : 0 for (let i = 0; i < iterMax; i++) { if (rate <= -1) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } let y if (Math.abs(rate) < epsMax) { @@ -519,7 +519,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp } rate -= y / dy } - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } ) } @@ -530,9 +530,9 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp type = type ? 1 : 0 if (rate === -1) { if (periods === 0) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NonZeroPeriods) } else { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } if (rate === 0) { @@ -548,7 +548,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('RRI'), (periods, present, future) => { if (present === 0 || (future < 0 && present > 0) || (future > 0 && present < 0)) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NaN) } return Math.pow(future / present, 1 / periods) - 1 @@ -560,7 +560,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('SLN'), (cost, salvage, life) => { if (life === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return (cost - salvage) / life } @@ -571,7 +571,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('SYD'), (cost: number, salvage: number, life: number, period: number) => { if (period > life) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.PeriodLong) } return ((cost - salvage) * (life - period + 1) * 2) / (life * (life + 1)) } @@ -584,20 +584,20 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp settlement = Math.round(settlement) maturity = Math.round(maturity) if (settlement >= maturity) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.StartEndDate) } const startDate = this.dateTimeHelper.numberToSimpleDate(settlement) const endDate = this.dateTimeHelper.numberToSimpleDate(maturity) if (endDate.year > startDate.year + 1 || (endDate.year === startDate.year + 1 && (endDate.month > startDate.month || (endDate.month === startDate.month && endDate.day > startDate.day)))) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.MaturityLong) } const denom = 360 - discount * (maturity - settlement) if (denom === 0) { return 0 } if (denom < 0) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.ValueLarge) } return 365 * discount / denom } @@ -610,20 +610,20 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp settlement = Math.round(settlement) maturity = Math.round(maturity) if (settlement >= maturity) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.StartEndDate) } const startDate = this.dateTimeHelper.numberToSimpleDate(settlement) const endDate = this.dateTimeHelper.numberToSimpleDate(maturity) if (endDate.year > startDate.year + 1 || (endDate.year === startDate.year + 1 && (endDate.month > startDate.month || (endDate.month === startDate.month && endDate.day > startDate.day)))) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.MaturityLong) } const denom = 360 - discount * (maturity - settlement) if (denom === 0) { return 0 } if (denom < 0) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.ValueLarge) } return 100 * (1 - discount * (maturity - settlement) / 360) } @@ -636,13 +636,13 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp settlement = Math.round(settlement) maturity = Math.round(maturity) if (settlement >= maturity) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.StartEndDate) } const startDate = this.dateTimeHelper.numberToSimpleDate(settlement) const endDate = this.dateTimeHelper.numberToSimpleDate(maturity) if (endDate.year > startDate.year + 1 || (endDate.year === startDate.year + 1 && (endDate.month > startDate.month || (endDate.month === startDate.month && endDate.day > startDate.day)))) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.MaturityLong) } return (100 - price) * 360 / (price * (maturity - settlement)) } @@ -707,7 +707,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp } } if (!posFlag || !negFlag) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } const n = vals.length const nom = npvCore(rrate, posValues) @@ -779,7 +779,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('IRR'), (range: SimpleRangeValue, guess: number) => { if (guess <= -1) { - return new CellError(ErrorType.VALUE) + return new CellError(ErrorType.VALUE, ErrorMessage.RateGuess) } const vals = this.arithmeticHelper.manyToExactNumbers(range.valuesFromTopLeftCorner()) @@ -791,7 +791,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp const hasPositive = vals.some(val => val > 0) const hasNegative = vals.some(val => val < 0) if (!hasPositive || !hasNegative) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.PositiveAndNegative) } return irrCore(vals, guess) @@ -809,7 +809,7 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp return this.runFunction(ast.args, state, this.metadata('XIRR'), (values: SimpleRangeValue, dates: SimpleRangeValue, guess: number) => { if (guess <= -1) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.RateGuess) } const cashFlows = sanitizeXirrRange(values.valuesFromTopLeftCorner()) @@ -828,13 +828,13 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp // A schedule needs at least two cash flows to define a rate of return. if (cashFlows.length < 2) { - return new CellError(ErrorType.NA) + return new CellError(ErrorType.NA, ErrorMessage.TwoCashFlows) } const hasPositive = cashFlows.some(value => value > 0) const hasNegative = cashFlows.some(value => value < 0) if (!hasPositive || !hasNegative) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.PositiveAndNegative) } return xirrCore(cashFlows, paymentDates, guess) @@ -882,7 +882,7 @@ function npvCore(rate: number, args: number[]): number | CellError { if (acc === 0) { continue } else { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } acc /= 1 + rate @@ -910,7 +910,7 @@ function irrCore(values: number[], guess: number): number | CellError { for (let i = 0; i < values.length; i++) { const factor = Math.pow(1 + rate, i) if (!isFinite(factor) || factor === 0) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } npv += values[i] / factor if (i > 0) { @@ -925,14 +925,14 @@ function irrCore(values: number[], guess: number): number | CellError { // Check if derivative is too small (avoid division by zero) if (Math.abs(dnpv) < epsMax) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } // Newton-Raphson step let newRate = rate - npv / dnpv if (!isFinite(newRate)) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } // Clamp: when Newton overshoots past -1, bisect between current rate and -1 @@ -948,7 +948,7 @@ function irrCore(values: number[], guess: number): number | CellError { rate = newRate } - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } /** @@ -1016,14 +1016,14 @@ function xirrCore(values: number[], dates: number[], guess: number): number | Ce const base = 1 + rate const factor = Math.pow(base, exponent) if (!isFinite(factor) || factor === 0) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } npv += values[i] / factor dnpv -= exponent * values[i] / (factor * base) } if (!isFinite(npv) || !isFinite(dnpv)) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } // Check for convergence. @@ -1033,13 +1033,13 @@ function xirrCore(values: number[], dates: number[], guess: number): number | Ce // Check if the derivative is too small (avoid division by zero). if (Math.abs(dnpv) < epsMax) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } // Newton-Raphson step. let newRate = rate - npv / dnpv if (!isFinite(newRate)) { - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } // Clamp: when Newton overshoots past -1, bisect between current rate and -1. @@ -1055,5 +1055,5 @@ function xirrCore(values: number[], dates: number[], guess: number): number | Ce rate = newRate } - return new CellError(ErrorType.NUM) + return new CellError(ErrorType.NUM, ErrorMessage.NoConvergence) } diff --git a/hyperformula/src/interpreter/plugin/InformationPlugin.ts b/hyperformula/src/interpreter/plugin/InformationPlugin.ts index 8c10ac05e..9a6589c15 100644 --- a/hyperformula/src/interpreter/plugin/InformationPlugin.ts +++ b/hyperformula/src/interpreter/plugin/InformationPlugin.ts @@ -441,7 +441,7 @@ export class InformationPlugin extends FunctionPlugin implements FunctionPluginT * @param _state */ public na(_ast: ProcedureAst, _state: InterpreterState): CellError { - return new CellError(ErrorType.NA) + return new CellError(ErrorType.NA, ErrorMessage.NaFunction) } /** diff --git a/hyperformula/src/interpreter/plugin/LookupPlugin.ts b/hyperformula/src/interpreter/plugin/LookupPlugin.ts index 625a68735..ac322708b 100644 --- a/hyperformula/src/interpreter/plugin/LookupPlugin.ts +++ b/hyperformula/src/interpreter/plugin/LookupPlugin.ts @@ -305,7 +305,7 @@ export class LookupPlugin extends FunctionPlugin implements FunctionPluginTypech } if (rangeValue.width() > 1 && rangeValue.height() > 1) { - return new CellError(ErrorType.NA) + return new CellError(ErrorType.NA, ErrorMessage.SearchRangeDimension) } const searchStrategy = rangeValue.width() === 1 ? this.columnSearch : this.rowSearch diff --git a/hyperformula/src/interpreter/plugin/MathPlugin.ts b/hyperformula/src/interpreter/plugin/MathPlugin.ts index e783d801f..6a037e808 100644 --- a/hyperformula/src/interpreter/plugin/MathPlugin.ts +++ b/hyperformula/src/interpreter/plugin/MathPlugin.ts @@ -251,7 +251,7 @@ export class MathPlugin extends FunctionPlugin implements FunctionPluginTypechec return this.runFunction(ast.args, state, this.metadata('QUOTIENT'), (nom: number, denom: number) => { if (denom === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return Math.trunc(nom / denom) } diff --git a/hyperformula/src/interpreter/plugin/ModuloPlugin.ts b/hyperformula/src/interpreter/plugin/ModuloPlugin.ts index 428f31e59..f296ee7cc 100644 --- a/hyperformula/src/interpreter/plugin/ModuloPlugin.ts +++ b/hyperformula/src/interpreter/plugin/ModuloPlugin.ts @@ -4,6 +4,7 @@ */ import {CellError, ErrorType} from '../../Cell' +import {ErrorMessage} from '../../error-message' import {ProcedureAst} from '../../parser' import {InterpreterState} from '../InterpreterState' import {InterpreterValue} from '../InterpreterValue' @@ -23,7 +24,7 @@ export class ModuloPlugin extends FunctionPlugin implements FunctionPluginTypech public mod(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('MOD'), (dividend: number, divisor: number) => { if (divisor === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return flooredRemainder(dividend, divisor) diff --git a/hyperformula/src/interpreter/plugin/NumericAggregationPlugin.ts b/hyperformula/src/interpreter/plugin/NumericAggregationPlugin.ts index 6f426c608..2bb056a75 100644 --- a/hyperformula/src/interpreter/plugin/NumericAggregationPlugin.ts +++ b/hyperformula/src/interpreter/plugin/NumericAggregationPlugin.ts @@ -273,7 +273,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } else { - return result.varSValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.varSValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } @@ -283,7 +283,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } else { - return result.varPValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.varPValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } @@ -301,7 +301,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } else { - return result.stdevSValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.stdevSValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } @@ -311,7 +311,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } else { - return result.stdevPValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.stdevPValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } @@ -411,7 +411,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } - return result.averageValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.averageValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } private doVarS(args: Ast[], state: InterpreterState): InternalScalarValue { @@ -420,7 +420,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } else { - return result.varSValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.varSValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } @@ -430,7 +430,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } else { - return result.varPValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.varPValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } @@ -440,7 +440,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } else { - return result.stdevSValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.stdevSValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } @@ -450,7 +450,7 @@ export class NumericAggregationPlugin extends FunctionPlugin implements Function if (result instanceof CellError) { return result } else { - return result.stdevPValue() ?? new CellError(ErrorType.DIV_BY_ZERO) + return result.stdevPValue() ?? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } } diff --git a/hyperformula/src/interpreter/plugin/RoundingPlugin.ts b/hyperformula/src/interpreter/plugin/RoundingPlugin.ts index 0753c30c8..034822b14 100644 --- a/hyperformula/src/interpreter/plugin/RoundingPlugin.ts +++ b/hyperformula/src/interpreter/plugin/RoundingPlugin.ts @@ -198,7 +198,7 @@ export class RoundingPlugin extends FunctionPlugin implements FunctionPluginType return 0 } if (significance === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } if ((value > 0) && (significance < 0)) { @@ -243,7 +243,7 @@ export class RoundingPlugin extends FunctionPlugin implements FunctionPluginType return 0 } if (significance === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } if ((value > 0) && (significance < 0)) { diff --git a/hyperformula/src/interpreter/plugin/StatisticalAggregationPlugin.ts b/hyperformula/src/interpreter/plugin/StatisticalAggregationPlugin.ts index c363bc135..aa3675552 100644 --- a/hyperformula/src/interpreter/plugin/StatisticalAggregationPlugin.ts +++ b/hyperformula/src/interpreter/plugin/StatisticalAggregationPlugin.ts @@ -173,7 +173,7 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func return coerced } if (coerced.length === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } const avg = mean(coerced) return coerced.reduce((a, b) => a + Math.abs(b - avg), 0) / coerced.length @@ -328,7 +328,7 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func return new CellError(ErrorType.NA, ErrorMessage.OneValue) } if (sigma === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return 1 - normal.cdf((mean(vals) - x) / (sigma / Math.sqrt(n)), 0, 1) } @@ -347,12 +347,12 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func return arrY } if (arrX.length <= 1 || arrY.length <= 1) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } const vx = variance(arrX, true) const vy = variance(arrY, true) if (vx === 0 || vy === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } const r = vx / vy const v = centralF.cdf(r, arrX.length - 1, arrY.length - 1) @@ -426,7 +426,7 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func let sum = 0 for (let i = 0; i < ret[0].length; i++) { if (ret[1][i] === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } sum += Math.pow(ret[0][i] - ret[1][i], 2) / ret[1][i] } @@ -463,7 +463,7 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func } const s = stdev(sub, true) if (s === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } const t = Math.abs(Math.sqrt(n) * mean(sub) / s) return tails * (1 - studentt.cdf(t, n - 1)) @@ -473,7 +473,7 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func } const s = (sumsqerr(arrX) + sumsqerr(arrY)) / (n + m - 2) if (s === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } const t = Math.abs((mean(arrX) - mean(arrY)) / Math.sqrt(s * (1 / n + 1 / m))) return tails * (1 - studentt.cdf(t, n + m - 2)) @@ -484,7 +484,7 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func const sx = variance(arrX, true) const sy = variance(arrY, true) if (sx === 0 && sy === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } const t = Math.abs((mean(arrX) - mean(arrY)) / Math.sqrt(sx / n + sy / m)) const v = Math.pow(sx / n + sy / m, 2) / (Math.pow(sx / n, 2) / (n - 1) + Math.pow(sy / m, 2) / (m - 1)) @@ -507,7 +507,7 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func const avg = mean(coerced) const s = stdev(coerced, true) if (s === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return coerced.reduce((a, b) => a + Math.pow((b - avg) / s, 3), 0) * n / (n - 1) / (n - 2) }) @@ -527,7 +527,7 @@ export class StatisticalAggregationPlugin extends FunctionPlugin implements Func const avg = mean(coerced) const s = stdev(coerced, false) if (s === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return coerced.reduce((a, b) => a + Math.pow((b - avg) / s, 3), 0) / n }) diff --git a/hyperformula/src/interpreter/plugin/StatisticalPlugin.ts b/hyperformula/src/interpreter/plugin/StatisticalPlugin.ts index 54fd6e2db..c1beeedb1 100644 --- a/hyperformula/src/interpreter/plugin/StatisticalPlugin.ts +++ b/hyperformula/src/interpreter/plugin/StatisticalPlugin.ts @@ -825,7 +825,7 @@ export class StatisticalPlugin extends FunctionPlugin implements FunctionPluginT (alpha: number, stddev: number, size: number) => { size = Math.trunc(size) if (size === 1) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } // eslint-disable-next-line // @ts-ignore diff --git a/hyperformula/src/interpreter/plugin/TrigonometryPlugin.ts b/hyperformula/src/interpreter/plugin/TrigonometryPlugin.ts index e1fe1b80e..b3d38f7c1 100644 --- a/hyperformula/src/interpreter/plugin/TrigonometryPlugin.ts +++ b/hyperformula/src/interpreter/plugin/TrigonometryPlugin.ts @@ -182,7 +182,7 @@ export class TrigonometryPlugin extends FunctionPlugin implements FunctionPlugin return this.runFunction(ast.args, state, this.metadata('ATAN2'), (x: number, y: number) => { if (x === 0 && y === 0) { - return new CellError(ErrorType.DIV_BY_ZERO) + return new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) } return Math.atan2(y, x) } @@ -191,7 +191,7 @@ export class TrigonometryPlugin extends FunctionPlugin implements FunctionPlugin public cot(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('COT'), - (arg) => (arg === 0) ? new CellError(ErrorType.DIV_BY_ZERO) : (1 / Math.tan(arg)) + (arg) => (arg === 0) ? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) : (1 / Math.tan(arg)) ) } @@ -209,7 +209,7 @@ export class TrigonometryPlugin extends FunctionPlugin implements FunctionPlugin public csc(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('CSC'), - (arg) => (arg === 0) ? new CellError(ErrorType.DIV_BY_ZERO) : (1 / Math.sin(arg)) + (arg) => (arg === 0) ? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) : (1 / Math.sin(arg)) ) } @@ -239,7 +239,7 @@ export class TrigonometryPlugin extends FunctionPlugin implements FunctionPlugin public coth(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('COTH'), - (arg) => (arg === 0) ? new CellError(ErrorType.DIV_BY_ZERO) : (1 / Math.tanh(arg)) + (arg) => (arg === 0) ? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) : (1 / Math.tanh(arg)) ) } @@ -257,7 +257,7 @@ export class TrigonometryPlugin extends FunctionPlugin implements FunctionPlugin public csch(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('CSCH'), - (arg) => (arg === 0) ? new CellError(ErrorType.DIV_BY_ZERO) : (1 / Math.sinh(arg)) + (arg) => (arg === 0) ? new CellError(ErrorType.DIV_BY_ZERO, ErrorMessage.DivisionByZero) : (1 / Math.sinh(arg)) ) } }