Skip to content
Open
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions hyperformula/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
},
Comment thread
cursor[bot] marked this conversation as resolved.
],
},
},
{
files: ['**/*.spec.ts'],
rules: {
Expand Down
10 changes: 10 additions & 0 deletions hyperformula/src/error-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
Expand All @@ -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.'
Expand All @@ -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.'
Expand All @@ -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.'
Expand All @@ -56,18 +61,23 @@ 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.'
public static LengthBounds = 'Length out of bounds.'
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.'
Expand Down
2 changes: 1 addition & 1 deletion hyperformula/src/interpreter/ArithmeticHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion hyperformula/src/interpreter/Interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion hyperformula/src/interpreter/plugin/ComplexPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
10 changes: 5 additions & 5 deletions hyperformula/src/interpreter/plugin/DatabasePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
})
}

Expand All @@ -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)
})
}

Expand All @@ -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)
})
}

Expand All @@ -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)
})
}

Expand Down
Loading
Loading