diff --git a/.eslintignore b/.eslintignore
index 37928812fe..ea6f691b24 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -11,11 +11,10 @@ src/interpreter/plugin/3rdparty
*.config.js
karma.*
doc
-test/unit/_setupFiles/*.js
+test/_setupFiles/*.js
# Auto-generated directories
commonjs
-coverage
dist
doc
es
diff --git a/.eslintrc.js b/.eslintrc.js
index 27fe09a0cd..7d6108d36b 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -15,7 +15,7 @@ module.exports = {
},
parserOptions: {
tsconfigRootDir: __dirname,
- project: './tsconfig.test.json',
+ project: './tsconfig.json',
createDefaultProgram: true,
},
extends: [
diff --git a/.github/codecov.yml b/.github/codecov.yml
deleted file mode 100644
index d0bd2428bd..0000000000
--- a/.github/codecov.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-codecov:
- require_ci_to_pass: yes
-
-coverage:
- range: 95..100
- round: down
- precision: 2
-
-comment:
- layout: "reach, diff, flags, files"
- behavior: new
- require_changes: false
- require_base: yes
- require_head: yes
diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml
deleted file mode 100644
index f70b6b37de..0000000000
--- a/.github/workflows/performance.yml
+++ /dev/null
@@ -1,69 +0,0 @@
-name: Performance
-permissions:
- contents: read
- pull-requests: write
-
-on:
- pull_request:
- types:
- - opened
- - reopened
- - synchronize # the head branch is updated from the base branch, new commits are pushed to the head branch, or the base branch is changed
-
-jobs:
- performance-test:
- strategy:
- matrix:
- node-version: [ '22' ]
- os: [ 'ubuntu-latest' ]
- name: Test performance
- runs-on: ${{ matrix.os }}
- steps:
- - name: Setup Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@56899e050abffc08c2b3b61f3ec6a79a9dc3223d # https://github.com/actions/setup-node/releases/tag/v1.4.4
- with:
- node-version: ${{ matrix.node-version }}
-
- - name: (base) Checkout
- uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # https://github.com/actions/checkout/releases/tag/v2.3.4
- with:
- ref: ${{ github.event.pull_request.base.sha }}
-
- - name: (base) Install dependencies
- run: |
- npm ci
-
- - name: (base) Run performance tests
- run: |
- npm run benchmark:write-to-file base.json
-
- - name: (head) Checkout
- uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # https://github.com/actions/checkout/releases/tag/v2.3.4
- with:
- clean: false
-
- - name: (head) Install dependencies
- run: |
- npm ci
-
- - name: (head) Run performance tests
- run: |
- npm run benchmark:write-to-file head.json
-
- - name: Compare the results
- run: |
- npm run benchmark:compare-benchmarks base.json head.json performance-report.md
-
- - name: Publish a comment - header
- uses: marocchino/sticky-pull-request-comment@6804b5ad49d19c10c9ae7cf5057352f7ff333f31 # https://github.com/marocchino/sticky-pull-request-comment/tree/v1.6.0
- with:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- message: |
- ## Performance comparison of head (${{ github.event.pull_request.head.sha }}) vs base (${{ github.event.pull_request.base.sha }})
-
- - name: Publish a comment - performance comparison report
- uses: marocchino/sticky-pull-request-comment@6804b5ad49d19c10c9ae7cf5057352f7ff333f31 # https://github.com/marocchino/sticky-pull-request-comment/tree/v1.6.0
- with:
- append: true
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- path: performance-report.md
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 51a87c303c..c289fd5b77 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -36,12 +36,7 @@ jobs:
- name: Run tests
run: |
- npm run test:unit.ci -- --coverage
-
- - name: Upload coverage to Codecov
- uses: codecov/codecov-action@6004246f47ab62d32be025ce173b241cd84ac58e # https://github.com/codecov/codecov-action/releases/tag/v1.0.13
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+ npm run test:ci
browser-tests:
strategy:
diff --git a/.gitignore b/.gitignore
index d4bb1b0997..5d6c7e54aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
.idea/
.vscode
/commonjs/
-/coverage/
/dist/
/doc/
/docs/api/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 76b86ebbd7..f5f3cd5669 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]
+## [3.2.0] - 2026-02-19
+
+### Added
+
+- Added a new function: IRR. [#1591](https://github.com/handsontable/hyperformula/issues/1591)
+- Added a new function: N. [#1585](https://github.com/handsontable/hyperformula/issues/1585)
+- Added a new function: VALUE. [#1592](https://github.com/handsontable/hyperformula/issues/1592)
+
+### Fixed
+
+- Fixed `Error Map maximum size exceeded` error when loading big spreadsheets. [#1602](https://github.com/handsontable/hyperformula/issues/1602)
+
+## [3.1.1] - 2025-12-18
+
### Fixed
- Fixed an issue where cells were not recalculated after adding, removing and renaming sheets. [#1116](https://github.com/handsontable/hyperformula/issues/1116)
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000000..5a11d26cda
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,95 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Project Overview
+
+HyperFormula is a headless spreadsheet engine written in TypeScript. It parses and evaluates Excel-compatible formulas and can run in browser or Node.js environments. The library implements ~400 built-in functions with support for custom functions, undo/redo, CRUD operations, and i18n (17 languages).
+
+## Build & Development Commands
+
+```bash
+npm install # Install dependencies
+npm run compile # TypeScript compilation to lib/
+npm run bundle-all # Full build: compile + bundle all formats
+npm run lint # Run ESLint
+npm run lint:fix # Auto-fix lint issues
+```
+
+## Testing
+
+```bash
+npm test # Full suite: lint + unit + browser + compatibility
+npm run test:unit # Jest unit tests only
+npm run test:watch # Jest watch mode (run tests on file changes)
+npm run test:coverage # Unit tests with coverage report
+npm run test:browser # Karma browser tests (Chrome/Firefox)
+npm run test:performance # Run performance benchmarks
+npm run test:compatibility # Excel compatibility tests
+```
+
+Test files are located in `test/unit/` and follow the pattern `*.spec.ts`.
+
+## Architecture
+
+### Core Components
+
+- **`src/HyperFormula.ts`** - Main engine class, public API entry point
+- **`src/parser/`** - Formula parsing using Chevrotain parser generator
+- **`src/interpreter/`** - Formula evaluation engine
+- **`src/DependencyGraph/`** - Cell dependency tracking and recalculation order
+- **`src/CrudOperations.ts`** - Create/Read/Update/Delete operations on sheets and cells
+
+### Function Plugins (`src/interpreter/plugin/`)
+
+All spreadsheet functions are implemented as plugins extending `FunctionPlugin`. Each plugin:
+- Declares `implementedFunctions` static property mapping function names to metadata
+- Uses `runFunction()` helper for argument validation, coercion, and array handling
+- Registers function translations in `src/i18n/languages/`
+
+To add a new function:
+1. Create or modify a plugin in `src/interpreter/plugin/`
+2. Add function metadata to `implementedFunctions`
+3. Implement the function method
+4. Add translations to all language files in `src/i18n/languages/`
+5. Add tests in `test/unit/interpreter/`
+
+### i18n (`src/i18n/languages/`)
+
+Function name translations for each supported language. When adding new functions, translations can be found at:
+- https://support.microsoft.com/en-us/office/excel-functions-translator-f262d0c0-991c-485b-89b6-32cc8d326889
+- http://dolf.trieschnigg.nl/excel/index.php
+
+## Output Formats
+
+The build produces multiple output formats:
+- `commonjs/` - CommonJS modules (main entry)
+- `es/` - ES modules (.mjs files)
+- `dist/` - UMD bundles for browsers
+- `typings/` - TypeScript declaration files
+
+## Contributing Guidelines
+
+- Create feature branches, never commit directly to master
+- Target the `develop` branch for pull requests
+- Add tests for all changes in `test/` folder
+- Run linter before submitting (`npm run lint`)
+- Maintain compatibility with Excel and Google Sheets behavior
+- In documentation, commit messages, pull request descriptions and code comments, do not mention Claude Code nor LLM models used for code generation
+
+## Response Guidelines
+
+- By default speak ultra-concisely, using as few words as you can, unless asked otherwise.
+- Focus solely on instructions and provide relevant responses.
+- Ask questions to remove ambiguity and make sure you're speaking about the right thing.
+- Ask questions if you need more information to provide an accurate answer.
+- If you don't know something, simply say, "I don't know," and ask for help.
+- Present your answer in a structured way, use bullet lists, numbered lists, tables, etc.
+- When asked for specific content, start the response with the requested info immediately.
+- When answering based on context, support your claims by quoting exact fragments of available documents.
+
+## Code Style
+
+- When generating code, prefer functional approach whenever possible (in JS/TS use filter, map and reduce functions).
+- Make the code self-documenting. Use meaningfull names for classes, functions, valiables etc. Add code comments only when necessary.
+- Add jsdocs to all classes and functions.
diff --git a/Makefile b/Makefile
index ea1c9af8db..5262600c8f 100644
--- a/Makefile
+++ b/Makefile
@@ -7,11 +7,7 @@ setup: ## Setup project
compile: ## Compile to javascript
@npm run compile
-test: ## Run tests
- @npm run test
-
-unit: ## Run unit tests
- @npm run test:unit
+check: typecheck lint ## Check whether code is working correctly (types + lint)
test-ci: ## Separate test configuration for CI environment
@npm run test
@@ -26,9 +22,6 @@ lint: ## Show linting errors
lint-fix: ## Fix linting errors
@npm run lint:fix
-coverage: ## Run tests and show coverage
- @npm run test:coverage
-
doc: ## Generate documentation
@npm run typedoc:build
@@ -65,6 +58,6 @@ verify-production-licenses:
help: ## Show all make commands
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
-.PHONY: test coverage benchmark doc servedoc
+.PHONY: test doc servedoc
.DEFAULT_GOAL := help
diff --git a/README.md b/README.md
index f82fba5e94..0573ebfe1e 100644
--- a/README.md
+++ b/README.md
@@ -22,11 +22,16 @@
---
+> 🚀 **We're hiring!** Join HyperFormula team as a **Senior Software Engineer**. [See the role and apply](https://handsontable.traffit.com/public/an/4b09e1395bf8ea42ef86db4c4657992c2f48673d).
+
HyperFormula is a headless spreadsheet built in TypeScript, serving as both a parser and evaluator of spreadsheet formulas. It can be integrated into your browser or utilized as a service with Node.js as your back-end technology.
## What HyperFormula can be used for?
+
HyperFormula doesn't assume any existing user interface, making it a general-purpose library that can be used in various business applications. Here are some examples:
+- Deterministic compute layer for AI & LLMs
+- Calculated fields in CRM and ERP software
- Custom spreadsheet-like app
- Business logic builder
- Forms and form builder
diff --git a/docs/.vuepress/components/HiringBanner.vue b/docs/.vuepress/components/HiringBanner.vue
new file mode 100644
index 0000000000..55207e6f57
--- /dev/null
+++ b/docs/.vuepress/components/HiringBanner.vue
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 6e95cc8a90..7ec541d842 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -11,6 +11,7 @@ const searchPattern = new RegExp('^/api', 'i');
module.exports = {
title: 'HyperFormula (v' + HyperFormula.version + ')',
description: 'HyperFormula is an open-source, high-performance calculation engine for spreadsheets and web applications.',
+ globalUIComponents: ['HiringBanner'],
head: [
// Import HF (required for the examples)
[ 'script', { src: 'https://cdn.jsdelivr.net/npm/hyperformula/dist/hyperformula.full.min.js' } ],
@@ -40,7 +41,7 @@ module.exports = {
new Sentry.Replay({
maskAllText: false,
blockAllMedia: false,
- }),
+ }),
],
});
};
@@ -62,6 +63,11 @@ module.exports = {
],
base: '/',
plugins: [
+ ['sitemap', {
+ hostname: 'https://hyperformula.handsontable.com',
+ exclude: ['/404.html'],
+ changefreq: 'weekly'
+ }],
searchBoxPlugin,
['container', examples()],
{
diff --git a/docs/.vuepress/public/robots.txt b/docs/.vuepress/public/robots.txt
new file mode 100644
index 0000000000..ef083139b3
--- /dev/null
+++ b/docs/.vuepress/public/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Allow: /
+
+Sitemap: https://hyperformula.handsontable.com/sitemap.xml
diff --git a/docs/guide/building.md b/docs/guide/building.md
index a781b05b8b..7827896429 100644
--- a/docs/guide/building.md
+++ b/docs/guide/building.md
@@ -68,10 +68,9 @@ pass in both of them because the library might be used
to be sure that both environments are fine.
* `npm run test` - runs the linter and all tests
-* `npm run test:unit` - runs unit tests
- * To run a test suite that matches a word, add a Jest `-t` flag. For example: `npm run test:unit -- -t 'SUMIF'` runs only the tests that match the word `SUMIF` within `describe()` or `it()`.
- * To run a specific test suite, pass the file name. For example: `npm run test:unit 'function-sumif.spec.ts'` runs only the unit tests from the file `function-sumif.spec.ts`.
-* `npm run test:coverage` - runs unit tests and generates code coverage
+* `npm run test:jest` - runs unit tests
+ * To run a test suite that matches a word, add a Jest `-t` flag. For example: `npm run test:jest -- -t 'SUMIF'` runs only the tests that match the word `SUMIF` within `describe()` or `it()`.
+ * To run a specific test suite, pass the file name. For example: `npm run test:jest 'function-sumif.spec.ts'` runs only the unit tests from the file `function-sumif.spec.ts`.
* `npm run test:browser` - runs tests in **karma** once and closes all open browsers
* To run a specific `spec` file or a test suite you can add a Karma `--spec` flag. For example: `npm run test:browser.debug -- --spec=matrix.spec.ts` runs `matrix.spec.ts` browser tests only
* `npm run test:browser.debug` - runs test in **karma** only in Chrome until you exit the process. It watches changes in `src` and `test` directories and rebuilds them automatically.
diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md
index 07d4075c62..dd97775232 100644
--- a/docs/guide/built-in-functions.md
+++ b/docs/guide/built-in-functions.md
@@ -176,6 +176,7 @@ Total number of functions: **{{ $page.functionsCount }}**
| FV | Returns the future value of an investment. | FV(Rate, Nper, Pmt[, Pv,[ Type]]) |
| FVSCHEDULE | Returns the future value of an investment based on a rate schedule. | FV(Pv, Schedule) |
| IPMT | Returns the interest portion of a given loan payment in a given payment period. | IPMT(Rate, Per, Nper, Pv[, Fv[, Type]]) |
+| IRR | Returns the internal rate of return for a series of cash flows. | IRR(Values[, Guess]) |
| ISPMT | Returns the interest paid for a given period of an investment with equal principal payments. | ISPMT(Rate, Per, Nper, Value) |
| MIRR | Returns modified internal value for cashflows. | MIRR(Flows, FRate, RRate) |
| NOMINAL | Returns the nominal interest rate. | NOMINAL(Effect_rate, Npery) |
@@ -491,6 +492,7 @@ Total number of functions: **{{ $page.functionsCount }}**
| LEN | Returns length of a given text. | LEN("Text") |
| LOWER | Returns text converted to lowercase. | LOWER(Text) |
| MID | Returns substring of a given length starting from Start_position. | MID(Text, Start_position, Length) |
+| N | Converts a value to a number. | N(Value) |
| PROPER | Capitalizes words given text string. | PROPER("Text") |
| REPLACE | Replaces substring of a text of a given length that starts at given position. | REPLACE(Text, Start_position, Length, New_text) |
| REPT | Repeats text a given number of times. | REPT("Text", Number) |
@@ -504,6 +506,7 @@ Total number of functions: **{{ $page.functionsCount }}**
| UNICHAR | Returns the character created by using provided code point. | UNICHAR(Number) |
| UNICODE | Returns the Unicode code point of a first character of a text. | UNICODE(Text) |
| UPPER | Returns text converted to uppercase. | UPPER(Text) |
+| VALUE | Parses a number, date, time, datetime, currency, or percentage from a text string. | VALUE(Text) |
[^non-odff]:
The return value of this function is compliant with the
diff --git a/docs/guide/custom-functions.md b/docs/guide/custom-functions.md
index c19f3494e7..d78408e61d 100644
--- a/docs/guide/custom-functions.md
+++ b/docs/guide/custom-functions.md
@@ -363,7 +363,7 @@ This demo contains the implementation of both the
[`DOUBLE_RANGE`](#advanced-custom-function-example) custom functions.