From d213a57c0f72ce8e678d4ccf12042550d6f3c459 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Mon, 11 May 2026 16:31:26 +0200 Subject: [PATCH 01/28] Fetch hyperformula-tests before running linter (#1672) ## Summary The lint GitHub Action did not check out the `hyperformula-tests` repository before running ESLint. As a result, lint errors inside files under `test/hyperformula-tests/` were silently skipped in CI and only surfaced when running `npm run lint` locally. This PR aligns `.github/workflows/lint.yml` with `.github/workflows/test.yml` so the linter sees the same source tree as the test jobs. ## Changes - `.github/workflows/lint.yml` - Add a `Checkout hyperformula-tests repository` step (using `DEPLOY_TOKEN`, target path `test/hyperformula-tests`). - Add a `Fetch hyperformula-tests and sync branches` step running `test/fetch-tests.sh`. - Name the existing main-repo checkout step for consistency with `test.yml`. --- .github/workflows/lint.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 015fbcdc1..936ef53fa 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,7 +28,18 @@ jobs: with: node-version: ${{ matrix.node-version }} - - uses: actions/checkout@722adc63f1aa60a57ec37892e133b1d319cae598 # https://github.com/actions/checkout/releases/tag/v2.0.0 + - name: Checkout main repository + uses: actions/checkout@722adc63f1aa60a57ec37892e133b1d319cae598 # https://github.com/actions/checkout/releases/tag/v2.0.0 + + - name: Checkout hyperformula-tests repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ssh-key: ${{ secrets.DEPLOY_TOKEN }} + repository: handsontable/hyperformula-tests + path: test/hyperformula-tests + + - name: Fetch hyperformula-tests and sync branches + run: cd test && ./fetch-tests.sh - name: Install dependencies run: npm ci From f62db38feaf3b7b883cba38aecdc1b37798b6703 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Tue, 19 May 2026 13:46:08 +0700 Subject: [PATCH 02/28] HF-161: document OFFSET function limitations (#1666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds a single canonical `### OFFSET function` sub-section under `## Nuances of the implemented functions` in `docs/guide/known-limitations.md`. Documents all six behavioral limits of the OFFSET function in HyperFormula, each backed either by an existing test in `unit/parser/offset-translation.spec.ts` or by a runtime check captured before this PR was opened. Removes the now-superseded one-row OFFSET entry from `docs/guide/list-of-differences.md` (per Kuba's decision in the 2026-04-21 meeting: *"można wtedy to stąd też usunąć. Żeby wszystko było tam jednak"*). The HF-vs-Excel/Sheets behavioral differences for OFFSET are not lost — they remain documented in full in `known-limitations.md` under the new sub-section, which is the canonical place for parse-time restrictions per the 04-21 decision to consolidate. > **Note on PR routing**: this PR replaces #1662 which was opened from a fork branch. Same content, now from upstream branch — CI will have full access (no fork-PR DEPLOY_TOKEN issue). Closing #1662 in favor of this one. ## Linked - Closes [#1572](https://github.com/handsontable/hyperformula/issues/1572) — *Docs: describe limitations of the OFFSET function* - Tracks the dynamic-args follow-up: [#910](https://github.com/handsontable/hyperformula/issues/910) - Out of scope (separate task): [#943](https://github.com/handsontable/hyperformula/issues/943) — restructuring `known-limitations` / `list-of-differences` / `specifications-and-limits` pages - Unblocked by: [handsontable/hyperformula-tests#12](https://github.com/handsontable/hyperformula-tests/pull/12) (merged 2026-05-14, cleared lint regression introduced by #1672) - Internal spec / tech rationale / implementation plan: tracked in the team workspace (not committed); summary in this PR description - Supersedes: #1662 ## Limits documented 1. First argument must be a single-cell reference (passing a range = parser error stored as cell value) 2. Row/column/height/width arguments must be static integer literals (parser error otherwise) 3. Height and width must be **bare** positive integer literals — `NUMBER` AST nodes only (unary `+`, parens, non-integers, values <1 all rejected at parse time) 4. Out-of-sheet target → `#REF!` error stored at parse time (not evaluation time), with the message *Resulting reference is out of the sheet* 5. `getCellFormula` returns the resolved reference, not the original `=OFFSET(...)` 6. Architectural rationale: OFFSET is rewritten at parse time into a plain cell reference, so introspection via `getCellFormula` shows the resolved reference rather than the call ## Runtime verification All six limits were verified against this branch's HEAD before publishing: ``` A. OFFSET in registered names: false (correct — OFFSET is parse-time, not registered) B. getCellFormula recovers: "=B1" (rewritten reference, NOT "=OFFSET(A1, 0, 1)") C. Out-of-sheet value: { value: "#REF!", message: "Resulting reference is out of the sheet." } ``` Tests covering all six limits live in `test/hyperformula-tests/unit/parser/offset-translation.spec.ts` (24 tests, lines 13–206 in the private repo). Run via `npm run test:jest -- --testPathPattern="offset-translation"`. ## Test plan - [ ] CI green on `handsontable/hyperformula` - [ ] Netlify deploy preview: [`/guide/known-limitations`](https://deploy-preview-1666--hyperformula-dev-docs.netlify.app/docs/guide/known-limitations.html) — verify the new `### OFFSET function` sub-section renders, including the four embedded `js` code blocks - [ ] Netlify deploy preview: [`/guide/list-of-differences`](https://deploy-preview-1666--hyperformula-dev-docs.netlify.app/docs/guide/list-of-differences.html) — verify the table is intact and the OFFSET row is gone ## Notes - This is docs-only — no CHANGELOG entry per project convention. - The internal `ErrorMessage.OutOfSheet` string is intentionally NOT quoted verbatim in the docs; the bullet describes the behavior instead, so future internal-string refactors don't break the docs. - Post-Codex review (2026-05-14): wording clarified to distinguish parser-error-as-cell-value vs API exception, and to specify that height/width accept only bare `NUMBER` literals (unary `+` etc. rejected). --- > [!NOTE] > **Low Risk** > Low risk docs-only change; the main risk is confusing users if the newly documented OFFSET constraints are inaccurate or drift from implementation. > > **Overview** > Adds a canonical **`### OFFSET function`** section to `docs/guide/known-limitations.md` describing HyperFormula’s parse-time rewriting behavior and the resulting constraints (single-cell first arg, static integer shifts/sizes, strict positive literal height/width, out-of-sheet `#REF!` at parse time, and `getCellFormula` returning the resolved reference), with small JS snippets. > > Removes the now-redundant `OFFSET` row from `docs/guide/list-of-differences.md` to consolidate documentation in one place. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 67ad2cd1d570e7f5745ae1db5582b3622cca384e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Kuba Sekowski --- docs/guide/known-limitations.md | 34 +++++++++++++++++++++++++++++++ docs/guide/list-of-differences.md | 1 - 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/guide/known-limitations.md b/docs/guide/known-limitations.md index 72753b9b2..3da1d2eec 100644 --- a/docs/guide/known-limitations.md +++ b/docs/guide/known-limitations.md @@ -38,3 +38,37 @@ you can't compare the arguments in a formula like this: * The INDEX function doesn't support returning whole rows or columns of the source range – it always returns the contents of a single cell. * The FILTER function accepts either single rows of equal width or single columns of equal height. In other words, all arrays passed to the FILTER function must have equal dimensions, and at least one of those dimensions must be 1. * Array-producing functions (e.g., SEQUENCE, FILTER) require their output dimensions to be determinable at parse time. Passing cell references or formulas as dimension arguments (e.g., `=SEQUENCE(A1)`) results in a `#VALUE!` error, because the output size cannot be resolved before evaluation. + +### OFFSET function + +HyperFormula resolves the OFFSET function at parse time rather than during evaluation. The parser inspects the arguments and rewrites the expression into a plain cell reference or range. This keeps the dependency graph accurate but imposes several restrictions. + +* The first argument must be a reference to a single cell. Passing a range causes the cell to store a parser error (the API call itself does not throw — read the error via `getCellValue`). + + ```js + // Cell A1 stores a parser error — the first argument must be a single cell, not a range + hf.setCellContents({ sheet: 0, row: 0, col: 0 }, '=OFFSET(A1:B1, 0, 0)'); + ``` + +* The row-shift, column-shift, height, and width arguments must be static integer literals known at parse time. Cell references and formulas passed as shift or size arguments cause the cell to store a parser error. + + ```js + // Cell A1 stores a parser error — the row-shift argument must be a static integer literal + hf.setCellContents({ sheet: 0, row: 0, col: 0 }, '=OFFSET(A1, C3, 0)'); + ``` + +* The height and width arguments must be bare positive integer literals (the parser accepts only `NUMBER` AST nodes). Unary `+` prefixes, parenthesised expressions, values less than 1, and non-integer values are rejected at parse time. + +* When the computed target falls outside the sheet, the parser stores a `#REF!` error in the cell at parse time (rather than during evaluation) with the message *Resulting reference is out of the sheet*. + + ```js + // Cell A1 stores #REF! + hf.setCellContents({ sheet: 0, row: 0, col: 0 }, '=OFFSET(A1, -1, 0)'); + ``` + +* OFFSET is resolved at parse time, so `getCellFormula` returns the computed reference, not the original `OFFSET` call. + + ```js + const hf = HyperFormula.buildFromArray([[1, 45, '=OFFSET(A1, 0, 1)']]); + hf.getCellFormula({ sheet: 0, row: 0, col: 2 }); // '=B1' + ``` diff --git a/docs/guide/list-of-differences.md b/docs/guide/list-of-differences.md index 9dac88022..32246f80b 100644 --- a/docs/guide/list-of-differences.md +++ b/docs/guide/list-of-differences.md @@ -35,7 +35,6 @@ See a full list of differences between HyperFormula, Microsoft Excel, and Google | TIMEVALUE function | =TIMEVALUE("14:31") | Type of the returned value: `CellValueDetailedType.NUMBER_TIME` (compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard) | Cell auto-formatted as **regular number** | Cell auto-formatted as **regular number** | | EDATE function | =EDATE(DATE(2019, 7, 31), 1) | Type of the returned value: `CellValueDetailedType.NUMBER_DATE`. This is non-compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard, which defines the return type as a Number, while describing it as a Date serial number through the function summary. | Cell auto-formatted as **date** | Cell auto-formatted as **regular number** | | EOMONTH function | =EOMONTH(DATE(2019, 7, 31), 1) | Type of the returned value: `CellValueDetailedType.NUMBER_DATE`. This is non-compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard, which defines the return type as a Number, while describing it as a Date serial number through the function summary. | Cell auto-formatted as **date** | Cell auto-formatted as **regular number** | -| OFFSET function | =OFFSET(A1:B1, 0, 0) | First parameter must be a **reference to a single cell**. | First parameter may be a single cell or a range. | First parameter may be a single cell or a range. | ## Built-in functions From 1a578df4d7bba227a9dcb9cd2dd226b04afb3e66 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Wed, 27 May 2026 10:02:32 +0200 Subject: [PATCH 03/28] Add Indonesian language pack (#1683) ### Context Original PR: https://github.com/handsontable/hyperformula/pull/1674 by https://github.com/Reckbeg ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [x] Additional language file, or a change to an existing language file (translations) - [ ] Change to the documentation ### Checklist: - [ ] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [ ] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [ ] My change is compatible with Microsoft Excel. - [ ] My change is compatible with Google Sheets. - [x] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide. --- > [!NOTE] > **Low Risk** > Translation-only addition with no engine logic changes; risk is limited to incorrect Indonesian function/error strings affecting formula parsing for `idID` users. > > **Overview** > Adds **Bahasa Indonesia** (`idID`) as a built-in HyperFormula language pack so formulas and cell errors can use Indonesian names (e.g. `JUMLAH`, `JIKA`, `#BAGI0!`). > > The new `src/i18n/languages/idID.ts` dictionary mirrors other locales (errors, function aliases, `NEW_SHEET_PREFIX: 'Lembar'`) and is re-exported from `src/i18n/languages/index.ts` for `hyperformula/i18n/languages/idID` imports. Docs now list **18** supported languages and include Indonesian in the supported-languages table; **CHANGELOG** records the addition under Unreleased. > > Also adds `.cursor/settings.json` enabling the superpowers plugin (unrelated to i18n). > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 918edd134df168ecf43f55d273d3b32433cd61fb. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: GreenFlux Co-authored-by: Claude Opus 4.6 Co-authored-by: Rofi Ibnu Haafizh --- .cursor/settings.json | 7 + CHANGELOG.md | 4 + docs/guide/i18n-features.md | 2 +- docs/guide/localizing-functions.md | 3 +- src/i18n/languages/idID.ts | 445 +++++++++++++++++++++++++++++ src/i18n/languages/index.ts | 1 + 6 files changed, 460 insertions(+), 2 deletions(-) create mode 100644 .cursor/settings.json create mode 100644 src/i18n/languages/idID.ts diff --git a/.cursor/settings.json b/.cursor/settings.json new file mode 100644 index 000000000..36b564bdb --- /dev/null +++ b/.cursor/settings.json @@ -0,0 +1,7 @@ +{ + "plugins": { + "superpowers": { + "enabled": true + } + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cf79c677..683e1da14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674) + ## [3.3.0] - 2026-05-20 ### Added diff --git a/docs/guide/i18n-features.md b/docs/guide/i18n-features.md index 90eba856c..14bb7b3a0 100644 --- a/docs/guide/i18n-features.md +++ b/docs/guide/i18n-features.md @@ -7,7 +7,7 @@ Configure HyperFormula to match the languages and regions of your users. ## Function names and errors -Each of HyperFormula's [built-in functions](built-in-functions.md) and [errors](types-of-errors.md) is available in [17 languages](localizing-functions.md#list-of-supported-languages). +Each of HyperFormula's [built-in functions](built-in-functions.md) and [errors](types-of-errors.md) is available in [18 languages](localizing-functions.md#list-of-supported-languages). You can easily [switch between languages](localizing-functions.md) ([`language`](../api/interfaces/configparams.md#language)). diff --git a/docs/guide/localizing-functions.md b/docs/guide/localizing-functions.md index ee777d838..1f8a752b3 100644 --- a/docs/guide/localizing-functions.md +++ b/docs/guide/localizing-functions.md @@ -1,7 +1,7 @@ # Localizing functions You can localize a function's ID and error -messages. Currently, HyperFormula supports 17 languages, with British English +messages. Currently, HyperFormula supports 18 languages, with British English as the default. To change the language all you need to do is import and @@ -128,6 +128,7 @@ You can localize your custom functions as well. For details, see the [Custom fun | Spanish | esES | | Swedish | svSE | | Turkish | trTR | +| Indonesian | idID | ## Demo diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts new file mode 100644 index 000000000..b07d9aad8 --- /dev/null +++ b/src/i18n/languages/idID.ts @@ -0,0 +1,445 @@ +/** + * @license + * Copyright (c) 2025 Handsoncode. All rights reserved. + */ + +import {RawTranslationPackage} from '..' + +const dictionary: RawTranslationPackage = { + errors: { + CYCLE: '#SIKLUS!', + DIV_BY_ZERO: '#BAGI0!', + ERROR: '#GALAT!', + NA: '#N/A', + NAME: '#NAME?', + NUM: '#ANGKA!', + REF: '#REF!', + SPILL: '#TUMPAH!', + VALUE: '#NILAI!', + }, + functions: { + FILTER: 'FILTER', + ADDRESS: 'ALAMAT', + 'ARRAY_CONSTRAIN': 'BATASAN.MATRIKS', + ARRAYFORMULA: 'RUMUS.MATRIKS', + ABS: 'ABS', + ACOS: 'ACOS', + ACOSH: 'ACOSH', + ACOT: 'ACOT', + ACOTH: 'ACOTH', + AND: 'DAN', + ARABIC: 'ARAB', + ASIN: 'ASIN', + ASINH: 'ASINH', + ATAN2: 'ATAN2', + ATAN: 'ATAN', + ATANH: 'ATANH', + AVERAGE: 'RATA-RATA', + AVERAGEA: 'RATA-RATAA', + AVERAGEIF: 'RATA-RATA.JIKA', + BASE: 'BASIS', + BIN2DEC: 'BIN.DEC', + BIN2HEX: 'BIN.HEX', + BIN2OCT: 'BIN.OKT', + BITAND: 'BITDAN', + BITLSHIFT: 'BIT.GESER.KIRI', + BITOR: 'BITATAU', + BITRSHIFT: 'BIT.GESER.KANAN', + BITXOR: 'BITXOR', + CEILING: 'BATAS.ATAS', + CHAR: 'KARAKTER', + CHOOSE: 'PILIH', + CLEAN: 'BERSIHKAN', + CODE: 'KODE', + COLUMN: 'KOLOM', + COLUMNS: 'KOLOM2', + CONCATENATE: 'GABUNG', + CORREL: 'KOREL', + COS: 'COS', + COSH: 'COSH', + COT: 'COT', + COTH: 'COTH', + COUNT: 'HITUNG', + COUNTA: 'HITUNG.TIDAK.KOSONG', + COUNTBLANK: 'HITUNG.KOSONG', + COUNTIF: 'HITUNG.JIKA', + COUNTIFS: 'HITUNG.JIKAH', + COUNTUNIQUE: 'HITUNG.UNIK', + CSC: 'CSC', + CSCH: 'CSCH', + CUMIPMT: 'KUM.BUNGA', + CUMPRINC: 'KUM.POKOK', + DATE: 'TANGGAL', + DATEDIF: 'SELISIH.TANGGAL', + DATEVALUE: 'NILAI.TANGGAL', + DAY: 'HARI', + DAYS360: 'HARI360', + DAYS: 'HARI2', + DB: 'DB', + DAVERAGE: 'DAVERAGE', + DCOUNT: 'DCOUNT', + DCOUNTA: 'DCOUNTA', + DGET: 'DGET', + DMAX: 'DMAX', + DMIN: 'DMIN', + DPRODUCT: 'DPRODUCT', + DSTDEV: 'DSTDEV', + DSTDEVP: 'DSTDEVP', + DSUM: 'DSUM', + DVAR: 'DVAR', + DVARP: 'DVARP', + DDB: 'DDB', + DEC2BIN: 'DES.BIN', + DEC2HEX: 'DES.HEX', + DEC2OCT: 'DES.OKT', + DECIMAL: 'DESIMAL', + DEGREES: 'DERAJAT', + DELTA: 'DELTA', + DOLLARDE: 'DOLAR.DES', + DOLLARFR: 'DOLAR.PEC', + EDATE: 'EDATE', + EFFECT: 'EFEKTIF', + EOMONTH: 'AKHIR.BULAN', + ERF: 'ERF', + ERFC: 'ERFC', + EVEN: 'GENAP', + EXACT: 'IDENTIK', + EXP: 'EXP', + FALSE: 'SALAH', + FIND: 'TEMUKAN', + FORMULATEXT: 'TEKS.RUMUS', + FV: 'FV', + FVSCHEDULE: 'FV.JADWAL', + HEX2BIN: 'HEX.BIN', + HEX2DEC: 'HEX.DEC', + HEX2OCT: 'HEX.OKT', + HLOOKUP: 'HLOOKUP', + HOUR: 'JAM', + HYPERLINK: 'TAUTAN', + IF: 'JIKA', + IFERROR: 'JIKA.GALAT', + IFNA: 'JIKA.NA', + IFS: 'JIKAS', + INDEX: 'INDEKS', + INT: 'BULAT.BAWAH', + INTERVAL: 'INTERVAL', + IPMT: 'IPMT', + IRR: 'IRR', + ISBINARY: 'ADALAH.BINER', + ISBLANK: 'KOSONG', + ISERR: 'ADALAH.GALAT', + ISERROR: 'ADALAH.KESALAHAN', + ISEVEN: 'ADALAH.GENAP', + ISFORMULA: 'ADALAH.RUMUS', + ISLOGICAL: 'ADALAH.LOGIS', + ISNA: 'ADALAH.NA', + ISNONTEXT: 'ADALAH.BUKAN.TEKS', + ISNUMBER: 'ADALAH.NOMOR', + ISODD: 'ADALAH.GANJIL', + ISOWEEKNUM: 'ISO.MINGGU', + ISPMT: 'ISPMT', + ISREF: 'ADALAH.REFERENSI', + ISTEXT: 'ADALAH.TEKS', + LEFT: 'KIRI', + LEN: 'PANJANG', + LN: 'LN', + LOG10: 'LOG10', + LOG: 'LOG', + LOWER: 'HURUF.KECIL', + MATCH: 'COCOK', + MAX: 'MAX', + MAXA: 'MAKSA', + MAXIFS: 'MAKS.JIKAS', + MAXPOOL: 'KOLOM.MAKS', + MEDIAN: 'MEDIAN', + MEDIANPOOL: 'KOLOM.MEDIAN', + MID: 'TENGAH', + MIN: 'MIN', + MINA: 'MINA', + MINIFS: 'MIN.JIKAS', + MINUTE: 'MENIT', + MIRR: 'MIRR', + MMULT: 'MMULT', + MOD: 'MOD', + MONTH: 'BULAN', + N: 'N', + NA: 'NA', + NETWORKDAYS: 'JUMLAH.HARI.KERJA', + 'NETWORKDAYS.INTL': 'JUMLAH.HARI.KERJA.INTL', + NOMINAL: 'NOMINAL', + NOT: 'BUKAN', + NOW: 'SEKARANG', + NPER: 'NPER', + NPV: 'NPV', + OCT2BIN: 'OKT.BIN', + OCT2DEC: 'OKT.DEC', + OCT2HEX: 'OKT.HEX', + ODD: 'GANJIL', + OFFSET: 'OFSET', + OR: 'ATAU', + PI: 'PI', + PMT: 'PMT', + PDURATION: 'PDURATION', + PRODUCT: 'PRODUK', + POWER: 'PANGKAT', + PPMT: 'PPMT', + PROPER: 'HURUF.AWAL.BESAR', + PV: 'PV', + RADIANS: 'RADIAN', + RAND: 'ACAK', + RATE: 'SUKU.BUNGA', + REPLACE: 'GANTI', + REPT: 'ULANG', + RIGHT: 'KANAN', + ROMAN: 'ROMAWI', + ROUND: 'BULAT', + ROUNDDOWN: 'BULAT.KEBAWAH', + ROUNDUP: 'BULAT.KEATAS', + ROW: 'BARIS', + ROWS: 'BARIS2', + RRI: 'RRI', + SEARCH: 'CARI', + SEC: 'SEC', + SECH: 'SECH', + SECOND: 'DETIK', + SEQUENCE: 'SEQUENCE', + SHEET: 'LEMBAR', + SHEETS: 'LEMBAR2', + SIN: 'SIN', + SINH: 'SINH', + SLN: 'GSL', + SPLIT: 'PISAH', + SQRT: 'AKAR', + STDEVA: 'STDEVA', + 'STDEV.P': 'STDEV.P', + STDEVPA: 'STDEVPA', + 'STDEV.S': 'STDEV.S', + SUBSTITUTE: 'SUBSTITUSI', + SUBTOTAL: 'SUBTOTAL', + SUM: 'JUMLAH', + SUMIF: 'JUMLAH.JIKA', + SUMIFS: 'JUMLAH.JIKAH', + SUMPRODUCT: 'PRODUK.JUMLAH', + SUMSQ: 'JUMLAH.KUADRAT', + SWITCH: 'BERALIH', + SYD: 'SYD', + T: 'T', + TAN: 'TAN', + TANH: 'TANH', + TBILLEQ: 'TBILL.SAMA', + TBILLPRICE: 'TBILL.HARGA', + TBILLYIELD: 'TBILL.HASIL', + TEXT: 'TEKS', + TEXTJOIN: 'TEXTJOIN', + TIME: 'WAKTU', + TIMEVALUE: 'NILAI.WAKTU', + TODAY: 'HARI.INI', + TRANSPOSE: 'TRANSPOS', + TRIM: 'HAPUS.SPASI', + TRUE: 'BENAR', + TRUNC: 'POTONG', + UNICHAR: 'UNIKARAKTER', + UNICODE: 'UNICODE', + UPPER: 'HURUF.BESAR', + VALUE: 'NILAI', + VARA: 'VARA', + 'VAR.P': 'VAR.P', + VARPA: 'VARPA', + 'VAR.S': 'VAR.S', + VLOOKUP: 'VLOOKUP', + WEEKDAY: 'HARI.DALAM.MINGGU', + WEEKNUM: 'MINGGU.KE', + WORKDAY: 'HARI.KERJA.SELESAI', + 'WORKDAY.INTL': 'HARI.KERJA.SELESAI.INTL', + XNPV: 'XNPV', + XOR: 'XATAU', + XLOOKUP: 'XLOOKUP', + YEAR: 'TAHUN', + YEARFRAC: 'FRAC.TAHUN', + 'HF.ADD': 'HF.ADD', + 'HF.CONCAT': 'HF.CONCAT', + 'HF.DIVIDE': 'HF.DIVIDE', + 'HF.EQ': 'HF.EQ', + 'HF.GT': 'HF.GT', + 'HF.GTE': 'HF.GTE', + 'HF.LT': 'HF.LT', + 'HF.LTE': 'HF.LTE', + 'HF.MINUS': 'HF.MINUS', + 'HF.MULTIPLY': 'HF.MULTIPLY', + 'HF.NE': 'HF.NE', + 'HF.POW': 'HF.POW', + 'HF.UMINUS': 'HF.UMINUS', + 'HF.UNARY_PERCENT': 'HF.UNARY_PERCENT', + 'HF.UPLUS': 'HF.UPLUS', + VARP: 'VARP', + VAR: 'VAR', + STDEVP: 'STDEVP', + STDEV: 'STDEV', + 'EXPON.DIST': 'EXPON.DIST', + FISHER: 'FISHER', + FISHERINV: 'FISHERINV', + GAMMA: 'GAMMA', + 'GAMMA.DIST': 'GAMMA.DIST', + GAMMALN: 'GAMMALN', + 'GAMMALN.PRECISE': 'GAMMALN.PRECISE', + 'GAMMA.INV': 'GAMMA.INV', + GAUSS: 'GAUSS', + FACT: 'FAKTOR', + FACTDOUBLE: 'FAKTOR.GANDA', + COMBIN: 'KOMBINASI', + COMBINA: 'KOMBINASIA', + GCD: 'FPB', + LCM: 'KPK', + MROUND: 'BULATKAN.MULTIPLE', + MULTINOMIAL: 'MULTINOMIAL', + QUOTIENT: 'HASIL.BAGI', + RANDBETWEEN: 'ACAK.ANTARA', + SERIESSUM: 'JUMLAH.SERI', + SIGN: 'TANDA', + SQRTPI: 'AKAR.PI', + SUMX2MY2: 'JUMLAH.X2MINY2', + SUMX2PY2: 'JUMLAH.X2TAMY2', + SUMXMY2: 'JUMLAH.XMINY2', + 'EXPONDIST': 'EXPONDIST', + GAMMADIST: 'GAMMADIST', + GAMMAINV: 'GAMMAINV', + 'BETA.DIST': 'BETA.DIST', + BETADIST: 'BETADIST', + 'BETA.INV': 'BETA.INV', + BETAINV: 'BETAINV', + 'BINOM.DIST': 'BINOM.DIST', + BINOMDIST: 'BINOMDIST', + 'BINOM.INV': 'BINOM.INV', + BESSELI: 'BESSELI', + BESSELJ: 'BESSELJ', + BESSELK: 'BESSELK', + BESSELY: 'BESSELY', + 'CHISQ.DIST': 'CHISQ.DIST', + 'CHISQ.DIST.RT': 'CHISQ.DIST.RT', + 'CHISQ.INV': 'CHISQ.INV', + 'CHISQ.INV.RT': 'CHISQ.INV.RT', + CHIDIST: 'CHIDIST', + CHIINV: 'CHIINV', + 'F.DIST': 'F.DIST', + 'F.DIST.RT': 'F.DIST.RT', + 'F.INV': 'F.INV', + 'F.INV.RT': 'F.INV.RT', + FDIST: 'FDIST', + FINV: 'FINV', + WEIBULL: 'WEIBULL', + 'WEIBULL.DIST': 'WEIBULL.DIST', + POISSON: 'POISSON', + 'POISSON.DIST': 'POISSON.DIST', + 'HYPGEOM.DIST': 'HYPGEOM.DIST', + HYPGEOMDIST: 'HYPGEOMDIST', + 'T.DIST': 'T.DIST', + 'T.DIST.2T': 'T.DIST.2T', + 'T.DIST.RT': 'T.DIST.RT', + 'T.INV': 'T.INV', + 'T.INV.2T': 'T.INV.2T', + TDIST: 'TDIST', + TINV: 'TINV', + LOGINV: 'LOGINV', + 'LOGNORM.DIST': 'LOGNORM.DIST', + 'LOGNORM.INV': 'LOGNORM.INV', + LOGNORMDIST: 'LOGNORMDIST', + 'NORM.DIST': 'NORM.DIST', + 'NORM.INV': 'NORM.INV', + 'NORM.S.DIST': 'NORM.S.DIST', + 'NORM.S.INV': 'NORM.S.INV', + NORMDIST: 'NORMDIST', + NORMINV: 'NORMINV', + NORMSDIST: 'NORMSDIST', + NORMSINV: 'NORMSINV', + PHI: 'PHI', + 'NEGBINOM.DIST': 'NEGBINOM.DIST', + 'NEGBINOMDIST': 'NEGBINOMDIST', + COMPLEX: 'KOMPLEKS', + IMABS: 'IMABS', + IMAGINARY: 'IMAGINER', + IMARGUMENT: 'IMARGUMEN', + IMCONJUGATE: 'IMKONJUGASI', + IMCOS: 'IMCOS', + IMCOSH: 'IMCOSH', + IMCOT: 'IMCOT', + IMCSC: 'IMCSC', + IMCSCH: 'IMCSCH', + IMDIV: 'IMBAGI', + IMEXP: 'IMEKSP', + IMLN: 'IMLN', + IMLOG10: 'IMLOG10', + IMLOG2: 'IMLOG2', + IMPOWER: 'IMPANGKAT', + IMPRODUCT: 'IMPRODUK', + IMREAL: 'IMRIIL', + IMSEC: 'IMSEC', + IMSECH: 'IMSECH', + IMSIN: 'IMSIN', + IMSINH: 'IMSINH', + IMSQRT: 'IMAKAR', + IMSUB: 'IMKURANG', + IMSUM: 'IMJUMLAH', + IMTAN: 'IMTAN', + LARGE: 'BESAR', + SMALL: 'KECIL', + PERCENTILE: 'PERCENTILE', + 'PERCENTILE.INC': 'PERCENTILE.INC', + 'PERCENTILE.EXC': 'PERCENTILE.EXC', + QUARTILE: 'QUARTILE', + 'QUARTILE.INC': 'QUARTILE.INC', + 'QUARTILE.EXC': 'QUARTILE.EXC', + AVEDEV: 'RATA.SIMPANG', + CONFIDENCE: 'KEYAKINAN', + 'CONFIDENCE.NORM': 'KEYAKINAN.NORM', + 'CONFIDENCE.T': 'KEYAKINAN.T', + DEVSQ: 'KUADRAT.SIMPANG', + GEOMEAN: 'RATA.GEO', + HARMEAN: 'RATA.HARM', + CRITBINOM: 'KRIT.BINOM', + 'COVARIANCE.P': 'KOVAR.P', + 'COVARIANCE.S': 'KOVAR.S', + 'COVAR': 'KOVAR', + PEARSON: 'PEARSON', + RSQ: 'RKUADRAT', + STANDARDIZE: 'STANDARISASI', + 'Z.TEST': 'Z.UJI', + ZTEST: 'ZUJI', + 'F.TEST': 'F.UJI', + FTEST: 'FUJI', + STEYX: 'STEYX', + SLOPE: 'KEMIRINGAN', + 'CHISQ.TEST': 'CHISQ.UJI', + CHITEST: 'CHIUJI', + 'T.TEST': 'T.UJI', + TTEST: 'TUJI', + SKEW: 'SKEW', + 'SKEW.P': 'SKEW.P', + WEIBULLDIST: 'WEIBULLDIST', + VARS: 'VARS', + TINV2T: 'TINV2T', + TDISTRT: 'TDISTRT', + TDIST2T: 'TDIST2T', + STDEVS: 'STDEVS', + FINVRT: 'FINVRT', + FDISTRT: 'FDISTRT', + CHIDISTRT: 'CHIDISTRT', + CHIINVRT: 'CHIINVRT', + COVARIANCEP: 'COVARIANCEP', + COVARIANCES: 'COVARIANCES', + LOGNORMINV: 'LOGNORMINV', + POISSONDIST: 'POISSONDIST', + SKEWP: 'SKEWP', + 'CEILING.MATH': 'BATAS.ATAS.MATEMATIK', + FLOOR: 'BATAS.BAWAH', + 'FLOOR.MATH': 'BATAS.BAWAH.MATEMATIK', + 'CEILING.PRECISE': 'BATAS.ATAS.PRESISI', + 'FLOOR.PRECISE': 'BATAS.BAWAH.PRESISI', + 'ISO.CEILING': 'ISO.BATAS.ATAS', + }, + langCode: 'idID', + ui: { + NEW_SHEET_PREFIX: 'Lembar', + }, +} + +export default dictionary diff --git a/src/i18n/languages/index.ts b/src/i18n/languages/index.ts index c5eaed084..dbffca47a 100644 --- a/src/i18n/languages/index.ts +++ b/src/i18n/languages/index.ts @@ -12,6 +12,7 @@ export {default as esES} from './esES' export {default as fiFI} from './fiFI' export {default as frFR} from './frFR' export {default as huHU} from './huHU' +export {default as idID} from './idID' export {default as itIT} from './itIT' export {default as nbNO} from './nbNO' export {default as nlNL} from './nlNL' From c7cfc9da20dbea9b8573320a0bc80cebe8648890 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Wed, 27 May 2026 12:33:47 +0200 Subject: [PATCH 04/28] Improve docs UX on narrow screens (#1684) ### Context Original PR https://github.com/handsontable/hyperformula/pull/1675 by https://github.com/Reckbeg --- > [!NOTE] > **Low Risk** > Documentation and CSS-only VuePress page tweaks with no runtime or library behavior changes. > > **Overview** > Improves **docs usability on small viewports** and clarifies **i18n naming sources** for maintainers. > > On **`built-in-functions`** and **`list-of-differences`**, adds inline CSS so tables on `.widePage` use **horizontal scrolling** (`overflow-x: auto`, block display) instead of breaking the layout on narrow screens. Also fixes a trivial heading whitespace on the differences page. > > **`DEV_DOCS.md`** documents how to pick localized built-in function names when Microsoft Excel does not support a locale: use **Google Sheets** function tables via the `hl` query parameter, and **fall back to English** when Sheets has no entry. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 9d0e126676a3362102cebff580cbeb54238b0004. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: GreenFlux Co-authored-by: Claude Opus 4.6 Co-authored-by: Rofi Ibnu Haafizh Co-authored-by: Hermes Bounty Bot --- DEV_DOCS.md | 6 ++++++ docs/guide/built-in-functions.md | 6 ++++++ docs/guide/list-of-differences.md | 8 +++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/DEV_DOCS.md b/DEV_DOCS.md index 3349b9e00..aede9c347 100644 --- a/DEV_DOCS.md +++ b/DEV_DOCS.md @@ -24,3 +24,9 @@ Each change to the production code (bugfixes, new features, improvements) must i HF supports internationalization and provides the localized function names for all built-in languages. When looking for the valid translations for the new functions, try these sources: - https://support.microsoft.com/en-us/office/excel-functions-translator-f262d0c0-991c-485b-89b6-32cc8d326889 - http://dolf.trieschnigg.nl/excel/index.php + +For languages not officially supported by Microsoft Excel, the two sources above do not apply. For these languages, use Google Sheets as the reference. Switch the `hl` query parameter to the target locale, for example: + +- https://support.google.com/docs/table/25273?hl=id (Indonesian) + +For functions that Google Sheets does not list either, fall back to the English name (matching the convention used by Excel in unsupported locales). diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index b1c998e60..5798f0404 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -11,6 +11,12 @@ It makes the page wider to accommodate large tables .page:has(.widePage) .page-nav /* footer links to the next and prev page */ { max-width: 1200px !important; /* override default max-width of 740px for this page */ } +/* Make wide tables horizontally scrollable on narrow screens */ +.page:has(.widePage) table { + display: block !important; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} ## Overview diff --git a/docs/guide/list-of-differences.md b/docs/guide/list-of-differences.md index 32246f80b..2ba4e9479 100644 --- a/docs/guide/list-of-differences.md +++ b/docs/guide/list-of-differences.md @@ -11,6 +11,12 @@ It makes the page wider to accommodate large tables .page:has(.widePage) .page-nav /* footer links to the next and prev page */ { max-width: 1200px !important; /* override default max-width of 740px for this page */ } +/* Make wide tables horizontally scrollable on narrow screens */ +.page:has(.widePage) table { + display: block !important; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} See a full list of differences between HyperFormula, Microsoft Excel, and Google Sheets. @@ -18,7 +24,7 @@ See a full list of differences between HyperFormula, Microsoft Excel, and Google **Contents:** [[toc]] -## General functionalities +## General functionalities | Functionality | Examples | HyperFormula | Google Sheets | Microsoft Excel | |----------------------------------------------------|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------| From 00e5c730a9e331289c3c8a7246da0dae007dd151 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Mon, 1 Jun 2026 11:32:38 +0200 Subject: [PATCH 05/28] Clean up in-repo dev docs (HF-65) (#1682) ### Context The in-repo developer documentation was scattered across several files with significant duplication: - `CLAUDE.md` duplicated build/test commands (also in `docs/guide/building.md`), contributing rules (also in `CONTRIBUTING.md` / `docs/guide/contributing.md`), and translation sources (also in `DEV_DOCS.md`). - `CONTRIBUTING.md` was an almost-verbatim copy of `docs/guide/contributing.md`. - `DEV_DOCS.md` contained only a small subset of what a developer needs. - Cursor-specific guidance did not exist. This PR removes the duplication and gives both humans and AI agents a single, predictable entry point. ### New structure | File | Role | | --- | --- | | `DEV_DOCS.md` | **Canonical** dev docs. Project overview, architecture, code style, definition of done, how to add a function, translation sources, plus quick links to the build / contributing / test docs. | | `docs/guide/building.md` | Full description of build, verify, docs, test, and lint commands, including output formats. Public, for users and devs. | | `docs/guide/contributing.md` | Full contributor guide. Public, for users and devs. | | `AGENTS.md` | Pointer to `DEV_DOCS.md` plus rules specific to AI agents (attribution, response style). | | `CLAUDE.md` | Pointer to `AGENTS.md`. | | `.cursor/rules/main.mdc` | Cursor rule that points to `AGENTS.md` (always applied). | | `CONTRIBUTING.md` | Pointer to `docs/guide/contributing.md`. | ### How did you test your changes? Manual review of the resulting file set: - `DEV_DOCS.md` contains everything a developer needs and links to `docs/guide/building.md`, `docs/guide/contributing.md`, `docs/guide/code-of-conduct.md`, `test/README.md`, `docs/README.md`, `CHANGELOG.md`, and `.github/pull_request_template.md`. - `AGENTS.md` links to `DEV_DOCS.md` and only adds agent-specific rules. - `CLAUDE.md`, `.cursor/rules/main.mdc`, and `CONTRIBUTING.md` are thin pointers with no duplicated content. - `docs/guide/building.md` is the only place that lists build/test/lint commands and bundle output formats. - `docs/guide/contributing.md` is unchanged (it already covered the contributor flow in full). ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues 1. HF-65 ### Checklist - [x] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [ ] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [ ] My change is compatible with Microsoft Excel. - [ ] My change is compatible with Google Sheets. - [ ] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide.
Open in Web Open in Cursor 
--------- Co-authored-by: Cursor Agent Co-authored-by: Kuba Sekowski Co-authored-by: marcin-kordas-hoc --- .cursor/rules/main.mdc | 6 +++ AGENTS.md | 44 ++++++++++++++++ CLAUDE.md | 96 +---------------------------------- CONTRIBUTING.md | 48 +----------------- DEV_DOCS.md | 111 ++++++++++++++++++++++++++++++++++------- docs/guide/building.md | 25 ++++++---- 6 files changed, 161 insertions(+), 169 deletions(-) create mode 100644 .cursor/rules/main.mdc create mode 100644 AGENTS.md mode change 100644 => 120000 CLAUDE.md mode change 100644 => 120000 CONTRIBUTING.md diff --git a/.cursor/rules/main.mdc b/.cursor/rules/main.mdc new file mode 100644 index 000000000..eef7fd7ce --- /dev/null +++ b/.cursor/rules/main.mdc @@ -0,0 +1,6 @@ +--- +description: Project-wide guidance for AI agents working in this repository +alwaysApply: true +--- + +All guidance for AI agents working in this repository lives in [AGENTS.md](mdc:AGENTS.md) at the repository root. Read it first. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..ee5b0ee7d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# AGENTS.md + +Instructions for AI coding agents (Cursor, Claude Code, Codex, Aider, and any other AI tool) working in this repository. + +## Start here + +Whatever you do, start by reading entire [DEV_DOCS.md](DEV_DOCS.md). Only then proceed to your task. + +## Other important resources + +- the repository [README.md](README.md) — high-level project description and quick install/usage +- the markdown files in [`docs/guide/`](docs/guide/) — user-facing guides (installation, configuration, built-in functions, custom functions, integrations, etc.) +- the markdown files in [`docs/api/`](docs/api/) — API reference (generated from JSDoc; run `npm run docs:build` if the folder is missing) + +Prefer reading these local files over fetching the rendered documentation from the web. + +## Response style + +- Be concise by default. Use as few words as possible unless the user asks for more detail. +- When the user asks for specific content, lead the response with the requested information. +- Structure answers with bullet lists, numbered lists, tables, or code blocks where useful. +- Ask clarifying questions when the request is ambiguous rather than guessing. +- If you do not know something, say so and ask for help. +- When answering from project documentation, quote the exact relevant fragments to support your claim. + +## Common ways agents fail + +This section is maintained by the team. Whenever an AI agent makes a mistake worth flagging, an item is added here describing what the agent did wrong and what it should have done instead. Read this list before starting any non-trivial task. + + + +_No items yet._ + +## Skills, MCPs, and other agent tools + +This section is maintained by the team. Skills, MCP servers, and other tools vetted as useful for AI agents working on this codebase are listed here. + + + +_No items yet._ diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 5a11d26cd..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,95 +0,0 @@ -# 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/CLAUDE.md b/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index d3f946c21..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,47 +0,0 @@ -# Contributing - -You are welcome to contribute to HyperFormula development. Your help -is much appreciated in any of the following topics: - -* Making pull requests - * Adding new functions - * Adding new features - * Improving the quality of the existing code - * Improving performance - * Improving documentation and public API -* Reporting bugs -* Suggesting improvements -* Suggesting new features - -## Good first issue - -Adding a new function will be a huge help for the library growth and -should not be too problematic for the first issue. Extending the -library of translations is also a good task to start with. -[Here](https://docs.google.com/spreadsheets/d/1UUskn4ZDDjLGSpO6kg73DOvabNoeqLbkJYyVfLyYlYw) -you can find a list of functions' translations. - -Visit [building]( https://handsontable.com/docs/hyperformula/guide/building.html) section to -get more info about the development process and check the list of commands you -can run in this project. Check the `/i18n` -folder in the project - all translations are kept just right there. -For the functions see the `interpreter/plugin` folder. Both of them -are a good starting point. - -## How to get started - -1. First, sign this -[Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2) -to allow us to use and publish your changes. -2. Always make your changes on a separate branch. This will speed up -the merging process. -3. Always make the target of your pull request the `develop` branch, -not `master`. -4. For any change you make, add test specs in the `test` folder. -5. Please lint the code. See the section about using linter. -6. Add a comprehensive description of all the changes. - -## Code of conduct - -By participating in this project, you are expected to uphold our -[Code of Conduct](https://github.com/handsontable/hyperformula/blob/master/CODE_OF_CONDUCT.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 120000 index 000000000..6c263eead --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +docs/guide/contributing.md \ No newline at end of file diff --git a/DEV_DOCS.md b/DEV_DOCS.md index aede9c347..24461cf36 100644 --- a/DEV_DOCS.md +++ b/DEV_DOCS.md @@ -1,27 +1,104 @@ -# Dev Docs +# Developer documentation -Random notes and things to know useful for maintainers and contributors. +Canonical reference for everyone working on the HyperFormula source code: maintainers, the internal team, and AI agents triggered by them. Everything a developer needs to know lives here or is linked from here. -## Definition of Done for the code changes +## Quick links -Each change to the production code (bugfixes, new features, improvements) must include these elements. They must be present in the pull request BEFORE requesting the code review. +- **[Building, testing, and linting](docs/guide/building.md)** — all `npm` commands and build outputs +- **[Test suite](test/README.md)** — smoke tests and how to attach the private test suite +- **[Public docs portal](https://hyperformula.handsontable.com/docs)** — main documentation +- **[Docs README](docs/README.md)** — how to run the docs portal locally +- **[Changelog](CHANGELOG.md)** +- **[Pull request template](.github/pull_request_template.md)** -- changes to the production code - - including changes to all supported language packs in `src/i18n/languages` directory (if applicable) -- automatic tests - - for bugfixes: at least one test reproducing the bug - - for new features: a set of tests describing the feature specification precisely - - pull requests from external contributors should include tests in `tests/` directory (they will be moved to the private repository by the internal team) - - internal team adds tests directly to the private repository (through a separate pull request) -- updates to documentation related to the change +## Repository layout + +``` +. +├── src/ # Source code +│ ├── HyperFormula.ts # Main engine class, public API entry point +│ ├── parser/ # Formula parsing (uses Chevrotain parser generator) +│ ├── interpreter/ # Formula evaluation engine +│ │ └── plugin/ # Built-in spreadsheet function plugins +│ ├── DependencyGraph/ # Cell dependency tracking and recalculation order +│ ├── CrudOperations.ts # Create/read/update/delete operations on sheets and cells +│ └── i18n/ # Function-name translations per language +├── test/ # Test suite +├── docs/ # Public documentation portal (VuePress) +│ ├── guide/ # Markdown guides (building, contributing, usage…) +│ ├── api/ # API reference (generated from JSDoc) +│ ├── .vuepress/ # VuePress configuration, theme, components +│ └── README.md # How to run the docs portal locally +├── script/ # Maintenance and release scripts +├── .github/ # CI workflows, issue and PR templates +├── DEV_DOCS.md # Canonical developer documentation (this file) +├── AGENTS.md # Guidance for AI agents +├── CONTRIBUTING.md # Guide for external contributors +├── README.md # Project overview +├── CHANGELOG.md +├── LICENSE.txt +├── package.json +└── tsconfig.json +``` + +## Architecture + +### Core modules + +- `src/HyperFormula.ts` — main engine class, public API entry point +- `src/parser/` — formula parsing (uses the [Chevrotain](https://chevrotain.io/) 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 an `implementedFunctions` static property mapping function names to metadata +- uses the `runFunction()` helper for argument validation, coercion, and array handling +- registers function translations in `src/i18n/languages/` + +## How to add a new function + +Adding a built-in function is similar to adding a [custom function](docs/guide/custom-functions.md), so that guide is a useful reference for the function-implementation patterns (argument metadata, return types, array handling). The built-in flow on top of that is: + +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/`. + +## Code style + +- Prefer a functional approach where possible (`filter`, `map`, `reduce`). +- Write self-documenting code: use meaningful names for classes, functions, and variables. Add code comments only when they explain intent the code itself cannot. +- Add JSDoc to all classes and functions. +- ESLint is the source of truth for formatting and code rules. Run `npm run lint` before submitting changes (see [building](docs/guide/building.md#run-the-linter)). + +## Definition of Done + +Each change to the production code (bugfix, new feature, or improvement) must include the following elements **before** requesting a code review: + +- Changes to the production code + - including changes to all supported language packs in `src/i18n/languages` (if applicable) +- Automatic tests + - for bug fixes: at least one test reproducing the bug + - for new features: a set of tests precisely describing the feature + - pull requests from external contributors should include tests in the `test/` directory (they will be moved to the private repository by the internal team) + - the internal team adds tests directly to the private repository (through a separate pull request) +- Updates to documentation related to the change - for breaking changes: a section in the migration guide -- technical documentation in the form of the jsdoc comments (high-level description of the concepts used in the more complex code fragments) -- changelog entry -- pull request description +- Technical documentation in the form of JSDoc comments (high-level description of the concepts used in more complex code fragments) +- Changelog entry +- Pull request description + +## Internationalization and function translations + +HyperFormula supports internationalization and provides localized function names for all built-in languages. Translation files live in `src/i18n/languages/`. New functions must include translations for all built-in languages. -## Sources of the function translations +When looking for the valid translations for new functions, try these sources: -HF supports internationalization and provides the localized function names for all built-in languages. When looking for the valid translations for the new functions, try these sources: - https://support.microsoft.com/en-us/office/excel-functions-translator-f262d0c0-991c-485b-89b6-32cc8d326889 - http://dolf.trieschnigg.nl/excel/index.php diff --git a/docs/guide/building.md b/docs/guide/building.md index 1e75be016..7d20b71a2 100644 --- a/docs/guide/building.md +++ b/docs/guide/building.md @@ -4,6 +4,21 @@ The build process uses Webpack and Babel, as well as npm tasks listed in package.json. During this process, the source located in the `src/*` directory is transformed into the output files. +The library is developed in TypeScript and the exact configuration +options can be found in `tsconfig.json`. To run the commands you need +to set up your environment to have `npm` or `yarn` properly installed. +After that, navigate to the project and run `npm install`. + +## Output formats + +The build produces the following output formats, all declared as +entry points in `package.json`: + +* `commonjs/` - CommonJS modules (main entry, used by Node.js and bundlers) +* `es/` - ES modules (`.mjs` files, used by tree-shaking bundlers) +* `dist/` - UMD bundles for direct use in the browser +* `typings/` - TypeScript declaration files (`.d.ts`) + **For UMD versions which reside in CDN:** * `./dist/hyperformula.js` - a full version which does not have @@ -14,16 +29,6 @@ have dependencies, they need to be added manually * `./dist/hyperformula.full.min.js` - a minified version with dependencies -There are also versions of builds in CommonJS, ES6, and TypeScript -definitions. They are marked in the package.json file. Based on -the tools used (Webpack, parsers, etc.), a proper build will be -respectively chosen. - -The library is developed in TypeScript and the exact configuration -options can be found in `tsconfig.json`. To run the commands you need -to set up your environment to have `npm` or `yarn` properly installed. -After that, navigate to the project and run `npm install`. - ## Build the project To build the project you can use the following commands: From 0e047eead222e8d118b9443854a5e9a020271226 Mon Sep 17 00:00:00 2001 From: Szymon Dolnik <42141039+Haxikowy@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:32:09 +0200 Subject: [PATCH 06/28] docs: fix SSR section framing in React integration guide (#1688) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Reframes the SSR section to clarify that the benefit of `dynamic(..., { ssr: false })` is keeping HyperFormula out of the **initial JS bundle sent to the browser**, not skipping the server-side render itself (which is already a non-issue since `useEffect` never runs on the server). ## Test Plan - [ ] Review the updated wording in `docs/guide/integration-with-react.md` --- > [!NOTE] > **Low Risk** > Documentation-only wording change with no runtime or API impact. > > **Overview** > Updates one sentence in the React integration guide’s SSR section so it no longer suggests `dynamic(..., { ssr: false })` is mainly about avoiding server render. The text now states the goal is keeping **HyperFormula out of the initial JS bundle sent to the browser** (still noting the library is a few hundred kB), which matches the earlier point that `useEffect` already makes the pattern SSR-safe. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 46d1d61652c027de7643c2a6dc0344f1b21a1ee1. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --- docs/guide/integration-with-react.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md index bdc7cd567..2a0820139 100644 --- a/docs/guide/integration-with-react.md +++ b/docs/guide/integration-with-react.md @@ -75,7 +75,7 @@ In development, React runs effects twice (mount → unmount → mount) to surfac ## Server-side rendering (Next.js App Router) -The component above is already SSR-safe — the engine is constructed in `useEffect`, which never runs on the server. If you still want to skip the initial bundle on the server (it is a few hundred kB), wrap it in a client-only dynamic import. +The component above is already SSR-safe — the engine is constructed in `useEffect`, which never runs on the server. If you still want to keep HyperFormula out of the initial JS bundle sent to the browser (it is a few hundred kB), wrap it in a client-only dynamic import. In the App Router, `dynamic(..., { ssr: false })` is only allowed inside a client component. Put the dynamic call in a `'use client'` wrapper and import the wrapper from your server page: From 79f2f15dc6b585b3ee9dd9f3c1d3343fc7634dd8 Mon Sep 17 00:00:00 2001 From: Szymon Dolnik <42141039+Haxikowy@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:54:13 +0200 Subject: [PATCH 07/28] docs: rewrite Vue integration guide around markRaw pattern (#1689) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Replaces the `SpreadsheetProvider` class example with the idiomatic `markRaw` pattern as the primary recommendation - Reframes the SSR section: HyperFormula is SSR-safe by default, `` is an optional optimization - Adds a TypeScript tip with a plain JS note - Tightens prose throughout (net -20 lines) ## Test plan - [x] Review the rendered docs page for clarity and correctness - [x] Verify all internal links resolve (`basic-operations.md`, `configuration-options.md`, etc.) - [x] Confirm the Vue 3 StackBlitz demo link still works --- > [!NOTE] > **Low Risk** > Documentation-only edits to integration guides; no application code, auth, or data paths change. > > **Overview** > Aligns the **Vue** and **React** integration guides around the same TypeScript-first docs pattern and makes **`markRaw`** the primary Vue integration story instead of a separate provider class. > > **Vue (`integration-with-vue.md`):** Drops the `SpreadsheetProvider` wrapper and `spreadsheet-provider.ts` example in favor of a single ` ``` -The class keeps the HyperFormula instance as a private field, so Vue's reactivity Proxy never reaches it. This is the same pattern used in the [Vue 3 demo](#demo). +`hf` is marked raw so Vue never proxies it — `values` is the only reactive piece. To mutate data, call any HyperFormula method (e.g. `setCellContents`) then reassign `values.value` to trigger a re-render. See [Basic operations](basic-operations.md) for the full mutation API. ## Server-side rendering (Nuxt) -The class above is already SSR-safe — HyperFormula has no browser-only API dependency. To skip the (otherwise wasted) server-side instantiation in Nuxt, wrap the component with ``. +HyperFormula has no browser-only API dependency. To skip server-side computation, wrap the component with ``. ## Troubleshooting @@ -89,16 +69,16 @@ If you encounter an error like Uncaught TypeError: Cannot read properties of undefined (reading 'licenseKeyValidityState') ``` -it means that Vue's reactivity system tried to deeply observe the HyperFormula instance. Vue wraps reactive objects in a `Proxy` that intercepts every property access; when that proxy reaches a non-trivial instance with its own internal state, identity checks and lazy-initialized maps break. The fix is to opt the instance out of reactivity with Vue's [`markRaw`](https://vuejs.org/api/reactivity-advanced.html#markraw): +it means that Vue's reactivity system tried to deeply observe the HyperFormula instance. Vue wraps reactive objects in a `Proxy` that intercepts every property access; when that proxy reaches a non-trivial instance with its own internal state, identity checks and lazy-initialized maps break. The fix is to opt the instance out of reactivity with [`markRaw`](https://vuejs.org/api/reactivity-advanced.html#markraw): ```typescript -import { markRaw } from 'vue'; -import { HyperFormula } from 'hyperformula'; +import { markRaw } from "vue"; +import { HyperFormula } from "hyperformula"; -const hfInstance = markRaw( +const hf = markRaw( HyperFormula.buildEmpty({ - licenseKey: 'gpl-v3', - }) + licenseKey: "gpl-v3", + }), ); ``` From d4195b56f28bc02ab7d2c7206c466c57dacb7565 Mon Sep 17 00:00:00 2001 From: KrzysztofZie Date: Tue, 9 Jun 2026 16:30:30 +0200 Subject: [PATCH 08/28] HF-238 - Review "Integration with Angular" guide and demo (#1691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Context - Added a modern Angular (v20+) section — an example in the same style as the demo: a service exposing a signal, a component using inject() + OnPush, a template with @if/@for, and bootstrap with provideZonelessChangeDetection. - Marked the old example as the older-versions variant (BehaviorSubject + async pipe), while fixing its compatibility: the component is now standalone with imports: [CommonModule], so it works without an NgModule. - Added a path for very old Angular (≤13) — an NgModule-based apps subsection at the end of the "older" section, completing the three tiers: modern → older/standalone → NgModule. - Moved the Demo section right under the modern Angular code (StackBlitz link, without describing the demo's internal standards). - Clarified the "Provider scope" and "Cleanup" notes to state they apply to both variants (they depend on DI scope, not on signals/RxJS). In short: the guide was rewritten to show the modern pattern (matching the demo) while preserving backward compatibility, with a clear split by Angular version. ### How did you test your changes? ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: 1. Fixes #... 2. 3. ### Checklist: - [ ] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [ ] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [ ] My change is compatible with Microsoft Excel. - [ ] My change is compatible with Google Sheets. - [ ] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide. --- > [!NOTE] > **Low Risk** > Documentation-only changes to the Angular integration guide; no runtime or library code affected. > > **Overview** > Rewrites the **Integration with Angular** guide into a **version-tiered** layout: a new **modern Angular (v20+)** path (signals, `inject()`, OnPush, `@if`/`@for`, `provideZonelessChangeDetection`) and a preserved **older** path (`BehaviorSubject` + `async` pipe). > > The legacy example is updated for **standalone** components (`standalone: true`, `imports: [CommonModule]`) and adds an **`NgModule`-based** subsection for Angular 13 and below. Sample sheet data in snippets changes from `[1, 2, '=A1+B1']` to `[1, 4, '=A1+B1']`. **Provider scope** and **Cleanup** notes now state they apply to both signal and RxJS variants. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 210c1ac53bada0f1a10302d00598f22514339d0b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Kuba Sekowski Co-authored-by: GreenFlux Co-authored-by: Claude Opus 4.6 Co-authored-by: Kuba Sekowski Co-authored-by: krzysztof.zielinski --- docs/guide/integration-with-angular.md | 130 ++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md index 491908f84..09b62cacb 100644 --- a/docs/guide/integration-with-angular.md +++ b/docs/guide/integration-with-angular.md @@ -4,9 +4,111 @@ The HyperFormula API is identical in an Angular app and in plain JavaScript. Thi Install with `npm install hyperformula`. For other options, see the [client-side installation](client-side-installation.md) section. -## Basic usage +## Basic usage (modern Angular) -Wrap the engine in an `@Injectable` service backed by a `BehaviorSubject`. Components subscribe to the observable with the `async` pipe, which handles subscription cleanup automatically. +For modern Angular (v20+) we recommend a service that exposes a **signal**, **standalone** components, the new control flow (`@if` / `@for`) and **zoneless** change detection. Wrap the engine in an `@Injectable` service and expose its values as a read-only signal; the template reads the signal directly and Angular refreshes the view whenever it changes. + +```typescript +// spreadsheet.service.ts +import { Injectable, signal } from '@angular/core'; +import { HyperFormula, type CellValue } from 'hyperformula'; + +@Injectable({ providedIn: 'root' }) +export class SpreadsheetService { + private readonly hf: HyperFormula; + + private readonly _values = signal([]); + readonly values = this._values.asReadonly(); + + constructor() { + this.hf = HyperFormula.buildFromArray( + [ + [1, 4, '=A1+B1'], + // your data rows go here + ], + { + licenseKey: 'gpl-v3', + // more configuration options go here + } + ); + this._values.set(this.hf.getSheetValues(0)); + } + + calculate() { + this._values.set(this.hf.getSheetValues(0)); + } + + reset() { + this._values.set([]); + } +} +``` + +Inject the service with `inject()`, expose its signal, and use `OnPush` change detection: + +```typescript +// spreadsheet.component.ts +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { SpreadsheetService } from './spreadsheet.service'; + +@Component({ + selector: 'app-spreadsheet', + templateUrl: './spreadsheet.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SpreadsheetComponent { + private readonly spreadsheetService = inject(SpreadsheetService); + + readonly values = this.spreadsheetService.values; + + runCalculations() { + this.spreadsheetService.calculate(); + } + + reset() { + this.spreadsheetService.reset(); + } +} +``` + +Read the signal in the template with the new control flow: + +```html + + + +@if (values().length) { + + @for (row of values(); track $index) { + + @for (cell of row; track $index) { + + } + + } +
{{ cell }}
+} +``` + +Bootstrap the app with zoneless change detection: + +```typescript +// main.ts +import { provideZonelessChangeDetection } from '@angular/core'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from './app/app.component'; + +bootstrapApplication(AppComponent, { + providers: [provideZonelessChangeDetection()], +}).catch((err) => console.error(err)); +``` + +> Signals require Angular 16+, the new control flow Angular 17+, and `provideZonelessChangeDetection` Angular 20+. For earlier versions, use the `BehaviorSubject` approach below. + + +## Basic usage (older Angular versions) + +For broader compatibility — including Angular versions without signals or zoneless change detection — wrap the engine in an `@Injectable` service backed by a `BehaviorSubject`. Components subscribe to the observable with the `async` pipe, which handles subscription cleanup automatically. ```typescript // spreadsheet.service.ts @@ -24,7 +126,7 @@ export class SpreadsheetService { constructor() { this.hf = HyperFormula.buildFromArray( [ - [1, 2, '=A1+B1'], + [1, 4, '=A1+B1'], // your data rows go here ], { @@ -45,17 +147,20 @@ export class SpreadsheetService { } ``` -Consume the service from a component and bind `values$ | async` in the template. Declare the component in your `AppModule` alongside `CommonModule`: +Consume the service from a component and bind `values$ | async` in the template. The component below is **standalone** (the default since Angular 17) and imports `CommonModule` directly, so it works without an `NgModule`. The structural directives `*ngIf` / `*ngFor` and the `async` pipe all come from `CommonModule`: ```typescript // spreadsheet.component.ts import { Component } from '@angular/core'; +import { CommonModule } from '@angular/common'; import { Observable } from 'rxjs'; import { SpreadsheetService } from './spreadsheet.service'; import { type CellValue } from 'hyperformula'; @Component({ selector: 'app-spreadsheet', + standalone: true, + imports: [CommonModule], templateUrl: './spreadsheet.component.html', }) export class SpreadsheetComponent { @@ -88,10 +193,27 @@ export class SpreadsheetComponent { ``` +### `NgModule`-based apps (Angular 13 and older) + +Standalone components require Angular 14 or newer. If your project still uses `NgModule`s (or targets Angular 13 or older), drop the `standalone: true` and `imports` fields from the component above, then declare it in your module and import `CommonModule` there instead: + +```typescript +// app.module.ts +@NgModule({ + declarations: [SpreadsheetComponent], + imports: [BrowserModule, CommonModule], +}) +export class AppModule {} +``` + +The service and template above are unchanged — only the way the component is wired up differs. + ## Notes ### Provider scope +The notes below apply to both the modern and older approaches — provider scope and cleanup depend on how the service is registered, not on whether it exposes a signal or a `BehaviorSubject`. + `providedIn: 'root'` makes the service an application-wide singleton — suitable when a single HyperFormula instance is shared across the app. For per-feature or per-component instances (for example, several independent reports on one screen), provide the service at the component level via `providers: [SpreadsheetService]`; the service is then created and destroyed alongside the component. ### Cleanup From 72205bd38d1a36397201b36cb34889ecec1fdea6 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 15 Jun 2026 18:15:22 +0700 Subject: [PATCH 09/28] docs(api): fix getAllSheetsSerialized/getRangeSerialized JSDoc for number cells (HF-219) (#1693) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the `getAllSheetsSerialized` / `getRangeSerialized` JSDoc examples that wrongly implied numeric strings are serialized as numbers — serialization round-trips, preserving the exact input type. Mirrors #1654. Documentation-only (a single source file changed), so no CHANGELOG entry per the docs-only convention. --- > [!NOTE] > **Low Risk** > Comment and example changes only; no runtime or serialization logic modified. > > **Overview** > **Documentation-only** updates to JSDoc for `getAllSheetsSerialized` and `getRangeSerialized` in `HyperFormula.ts`. > > The docs now state that **non-formula cells keep the exact `RawCellContent` type they were set with** (string `'1'` vs number `1`), and the embedded examples were corrected so numeric literals are numbers in sample input/output instead of implying string digits are coerced to numbers on serialize. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 691b8b1c68523e4e5d8fc6d2c4364ab680f3fbf3. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). Co-authored-by: Claude Opus 4.8 Co-authored-by: Kuba Sekowski --- src/HyperFormula.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/HyperFormula.ts b/src/HyperFormula.ts index 566957270..ec342be6b 100644 --- a/src/HyperFormula.ts +++ b/src/HyperFormula.ts @@ -995,15 +995,19 @@ export class HyperFormula implements TypedEmitter { /** * Returns formulas or values of all sheets in a form of an object which property keys are strings and values are 2D arrays of [[RawCellContent]]. * + * Each non-formula cell is serialized to the exact value it was set with, preserving its type. + * For example, a cell set with the string `'1'` is serialized as the string `'1'`, while a cell set with the number `1` is serialized as the number `1`. + * * @throws [[EvaluationSuspendedError]] when the evaluation is suspended * * @example * ```js * const hfInstance = HyperFormula.buildFromArray([ - * ['1', '2', '=A1+10'], + * ['1', 2, '=A1+10'], * ]); * - * // should return all sheets serialized content: { Sheet1: [ [ 1, 2, '=A1+10' ] ] } + * // should return all sheets serialized content: { Sheet1: [ [ '1', 2, '=A1+10' ] ] } + * // note: the string '1' stays a string and the number 2 stays a number * const allSheetsSerialized = hfInstance.getAllSheetsSerialized(); * ``` * @@ -2494,6 +2498,9 @@ export class HyperFormula implements TypedEmitter { /** * Returns serialized cells in given range. * + * Each non-formula cell is serialized to the exact value it was set with, preserving its type + * (e.g., a cell set with the string `'2'` is serialized as the string `'2'`, while a cell set with the number `2` is serialized as the number `2`). + * * @param {SimpleCellRange} source - rectangular range * * @throws [[ExpectedValueOfTypeError]] if source is of wrong type @@ -2503,9 +2510,9 @@ export class HyperFormula implements TypedEmitter { * @example * ```js * const hfInstance = HyperFormula.buildFromArray([ - * ['=SUM(1, 2)', '2', '10'], - * ['5', '6', '7'], - * ['40', '30', '20'], + * ['=SUM(1, 2)', 2, 10], + * [5, 6, 7], + * [40, 30, 20], * ]); * * // should return serialized cell content for the given range: From 8bfa558781813028137e0578e6abba16463f3ca9 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Thu, 25 Jun 2026 16:09:47 +0700 Subject: [PATCH 10/28] docs(guide): named ranges in formulas + fix limitations pointer (HF-222) (#1695) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents how a range-valued named expression behaves in a formula and fixes the inaccurate limitations bullet (HF-222). - `named-expressions.md`: new **Using named ranges in formulas** section — behavior as function argument (full range), operator operand (implicit intersection → single cell; array mode → `#VALUE!`), and bare reference (`#VALUE!`). - `known-limitations.md`: replaces the inaccurate top-level named-ranges bullet with a neutral pointer to that section. Paired characterization tests: hyperformula-tests branch `hf-222-named-ranges` (matching branch for fetch-tests). Docs-only; no CHANGELOG. --- > [!NOTE] > **Low Risk** > Documentation-only changes with no runtime or API impact. > > **Overview** > Documents **how range-valued named expressions behave in formulas** and corrects a misleading limitations note. > > **`named-expressions.md`** adds **Using named ranges in formulas**: full range when passed to functions; implicit intersection to one cell (or `#VALUE!`) when used with operators; `#VALUE!` for a bare range reference. It also contrasts default evaluation with **`useArrayArithmetic: true`** (e.g. `=SUM(myRange + 1)` vs element-wise sum). > > **`known-limitations.md`** drops the inaccurate claim that named ranges can’t be used in comparisons like `=IF(firstRange>secondRange, …)` and points readers to the new section instead. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 284436779db581459228368560a09bae00c8d52c. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Claude Opus 4.8 --- docs/guide/known-limitations.md | 4 +--- docs/guide/named-expressions.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/guide/known-limitations.md b/docs/guide/known-limitations.md index 3da1d2eec..e58523372 100644 --- a/docs/guide/known-limitations.md +++ b/docs/guide/known-limitations.md @@ -14,9 +14,7 @@ the full evaluation of expressions and condition statements. The most prominent example of this behavior is the "IF" function which returns a cycle error regardless of whether TRUE or FALSE causes a circular reference. -* There is no data validation against named ranges. For example, -you can't compare the arguments in a formula like this: -=IF(firstRange>secondRange, TRUE, FALSE). +* Named ranges behave differently depending on where they are used in a formula. For details, see [Using named ranges in formulas](named-expressions.md#using-named-ranges-in-formulas). * [Custom functions](custom-functions.md) don't automatically recalculate the size of their [result arrays](custom-functions.md#return-an-array-of-data) when the formula dependencies change. * There is no relative referencing in named ranges. * The library doesn't offer (at least not yet) the following features: diff --git a/docs/guide/named-expressions.md b/docs/guide/named-expressions.md index 939e13148..625b80416 100644 --- a/docs/guide/named-expressions.md +++ b/docs/guide/named-expressions.md @@ -87,6 +87,21 @@ hfInstance.setCellContents({sheet: 0, col: 2, row: 0}, [['=SUM(SalesData)']]); hfInstance.setCellContents({sheet: 0, col: 2, row: 1}, [['=SUM(SalesData) * TaxRate']]); ``` +## Using named ranges in formulas + +A named expression that resolves to a range of cells behaves differently depending on where it is used: + +- **As a function argument** — it works as expected. `=SUM(myRange)`, `=COUNT(myRange)`, and `=INDEX(myRange, 1, 1)` all operate on the full range. +- **As an operand of an operator** — the range is reduced to a single cell before the operation. In `=myRange + 1`, only the cell of the range that shares the formula's row (for a vertical range) or column (for a horizontal range) is used. If the formula's row or column falls outside the range, or the range is two-dimensional, the result is a `#VALUE!` error. +- **As a bare reference** — `=myRange` on its own returns a `#VALUE!` error; a range cannot be placed directly into a single cell. + +In the default mode the range is reduced before the operator runs, so `=SUM(myRange + 1)` adds 1 to that single reduced value rather than to every element (for a formula in row 1 of a vertical range, the result is `SUM(A1 + 1)`). + +When array arithmetic is enabled (`useArrayArithmetic: true`), named ranges still work as function arguments and aggregate correctly, but as an operand they behave differently from the default mode: + +- A bare `=myRange + 1` does not spill — it returns a `#VALUE!` error rather than producing one result per element. +- Inside an aggregate the operator becomes element-wise. `=SUM(myRange + 1)` adds 1 to every element and then sums, so for `myRange` covering values `1..5` it returns `20` (`SUM(2, 3, 4, 5, 6)`), not the single reduced value of the default mode. + ## Available methods These are the basic methods that can be used to add and manipulate named From cffd92277eba2fa26da15248194617f846d1484c Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 6 Jul 2026 14:32:05 +0200 Subject: [PATCH 11/28] HF-24: add stringifyCurrency config callback for TEXT (#1665) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Adds a `stringifyCurrency` config option mirroring the existing `stringifyDateTime` / `stringifyDuration` callbacks. When set, the `TEXT` function consults the callback before falling through to the built-in number formatter, so users can plug in locale-aware currency formatting (for example via `Intl.NumberFormat` or a third-party library) without bringing currency data into the HyperFormula core. The default implementation returns `undefined` so existing TEXT behavior is preserved bit-for-bit **for non-LCID-tagged formats**. LCID-tagged currency strings (`[$SYMBOL-LCID]`) now correctly skip the date/time parser (see **Added** in CHANGELOG); that is a separate behavioral fix, not a regression. > **Note on PR routing**: this PR replaces #1661 which was opened from a fork branch (`marcin-kordas-hoc`). Fork-side PRs cannot access the `DEPLOY_TOKEN` secret needed to clone the private `hyperformula-tests` repo, so 3 of the matrix checks (`Test performance`, `unit-tests`, `browser-tests`) failed structurally there. Same content, same SHA (`0246ce0bcbbed09ac9bf5e24af38b0d8aa1ac099`), now from upstream branch — CI will have full access. Closing #1661 in favor of this one. ## Linked - Spec: `agents/hyperformula/docs/specs/2026-04-21-hf-24-currency-in-text.md` - Tech rationale: `agents/hyperformula/docs/specs/2026-04-24-hf-24-tech-rationale.md` - Implementation plan: `agents/hyperformula/docs/specs/2026-04-27-hf-24-stringify-currency-plan.md` - Supersedes: #1661 ## Tests Tests added in the matching `feature/hf-24-stringify-currency` branch of `handsontable/hyperformula-tests`. Coverage: - default callback returns `undefined` - custom callback intercepts currency formats - callback opts out (returns `undefined`) → fall-through to `numberFormat` - date / duration formats are not intercepted by `stringifyCurrency` - five Excel format strings actively handled by the docs `Intl.NumberFormat` adapter (USD shorthand, EUR via LCID, JPY via LCID, PLN via LCID, accounting two-section), plus a fall-through case demonstrating opt-out ## Notes - **PLN format string change**: the spec example originally used `#,##0.00 "zł"` (trailing quoted symbol). HF's formula parser does not accept embedded quotes inside TEXT format strings, so the docs example and the corresponding test were swapped to use `[$zł-415] #,##0.00` (LCID-tagged symbol). The adapter still recognizes the trailing-quote pattern for users invoking the callback outside HyperFormula. - **EUR / JPY assertion shapes reflect ICU output, not the original plan**: tests assert `'1.234,50 €'` (symbol-trailing) for `[$€-2]` and `'¥1,235'` (full-width yen sign, no space) for `[$¥-411]` because that is what `Intl.NumberFormat('de-DE'/'ja-JP', ...)` actually produces on modern Node ICU. NBSP normalization in tests covers both `\u00A0` and `\u202F` variants for ICU build robustness. - **No-LCID `[$SYMBOL]` boundary**: the example regex requires the `-LCID` segment. A bare `[$USD]` pattern is not handled by the adapter and falls through to the built-in `numberFormat`, whose handling of `[$...]` in HyperFormula is implementation-defined. Test `docs adapter does not handle [$SYMBOL] without LCID segment` documents the boundary. - **Additional minor changes** (not in summary): (a) two broken anchor links fixed in `docs/guide/built-in-functions.md`; (b) `docs/guide/i18n-features.md` replaces a stale `currencySymbol` code example with a cross-reference; (c) `tsconfig.test.json` adds `test-utils` to `include`; (d) `docs/guide/known-limitations.md` — new bullet for the TEXT embedded-quote limitation (can't use `""` escape in format strings; use LCID-tagged form or `stringifyCurrency` callback); (e) `docs/guide/date-and-time-handling.md` — adds cross-reference paragraph to `currency-handling.md`; (f) `docs/guide/list-of-differences.md` — updates the "No currency formatting in TEXT" row with callback instructions; (g) `.eslintignore` — adds `test-utils/snippets` exclusion; (h) `test-utils/snippets/*.generated.ts` — generated from the docs and **not committed**; regenerated before every test run by inlining `npm run snippets:extract &&` into the `test:jest`/`test:ci`/`test:browser` scripts (`npm-run-all` does not fire `pre*` hooks) and git-ignored, so the docs stay the single source of truth. (i) `script/extract-doc-snippets.js` (257 LOC) — new codegen script that walks the docs for `` markers and syncs them into `test-utils/snippets/*.generated.ts`. Heavy for one snippet but designed for future docs expansion; see inline docstring for design rationale. - **Performance benchmark shows +1.9% to +8.7% across metrics** vs the post-merge base (`456adddff` = develop with HF-85 DatabasePlugin). HF-24's runtime impact is one extra dispatcher call in `format()` per `TEXT` invocation, which the Sheet A/B/T benchmarks don't exercise. The variance is most likely benchmark noise or HF-85 import overhead carried in via the develop merge, not HF-24-specific. - **`Maybe` in callback types**: `stringifyCurrency`, `stringifyDateTime`, and `stringifyDuration` all declare return type as `Maybe` (= `string | undefined`). This is consistent public API surface shared by all three siblings; the alias is re-exported from HyperFormula's type definitions. Changing only `stringifyCurrency` to bare `string | undefined` would create asymmetry. A follow-up can align all three if desired. ## Test plan - [x] CI green on `handsontable/hyperformula` PR - [x] CI green on `handsontable/hyperformula-tests` PR (matching branch) - [x] Manual: built docs locally — `vuepress build docs` returns EXIT 0 with 215 sitemap entries; new `currency-handling.md` guide renders with the `Currency input` + `Currency output` sections (sidebar wired under Internationalization) per Kuba's review feedback --- **Private tests PR:** handsontable/hyperformula-tests#10 --- > [!NOTE] > **Medium Risk** > Every `TEXT` invocation goes through an extra formatter dispatch step, and LCID-tagged currency format strings change behavior (intentional fix); misconfigured callbacks can alter formula output broadly. > > **Overview** > Introduces **`stringifyCurrency`**, a config callback parallel to `stringifyDateTime` / `stringifyDuration`, so `TEXT` can delegate currency output to custom formatters (e.g. `Intl.NumberFormat`) while the default no-op preserves existing behavior for ordinary formats. > > **Runtime:** `format()` now calls `stringifyCurrency` first. `defaultStringifyDateTime` and `defaultStringifyDuration` skip Excel **`[$SYMBOL-LCID]`** currency tags so those strings are no longer mangled by date/time parsing and can reach the number formatter or a user callback. Config wiring lives in `Config.ts`, `ConfigParams.ts`, and `defaultStringifyCurrency` in `format.ts`. > > **Docs & DX:** New **Currency handling** guide, sidebar entry, compatibility/differences updates, changelog entries, and expanded `DEV_DOCS` testing guidance. **`script/extract-doc-snippets.js`** generates `test-utils/snippets/*.generated.ts` from `` blocks in docs; `test:jest` / `test:ci` / `test:browser` run `snippets:extract` first; generated files are gitignored. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 9cc8d6ceb5f2462b160064113aa504e1990a6203. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Kuba Sekowski Co-authored-by: Kuba Sekowski --- .eslintignore | 1 + .gitignore | 4 + CHANGELOG.md | 2 + DEV_DOCS.md | 41 +-- docs/.vuepress/config.js | 1 + docs/guide/built-in-functions.md | 6 +- .../guide/compatibility-with-google-sheets.md | 4 + .../compatibility-with-microsoft-excel.md | 4 + docs/guide/currency-handling.md | 211 ++++++++++++++ docs/guide/date-and-time-handling.md | 2 + docs/guide/i18n-features.md | 8 +- docs/guide/known-limitations.md | 1 + docs/guide/list-of-differences.md | 2 +- package.json | 7 +- script/extract-doc-snippets.js | 257 ++++++++++++++++++ src/Config.ts | 7 +- src/ConfigParams.ts | 16 ++ src/format/format.ts | 99 +++++++ tsconfig.test.json | 2 +- 19 files changed, 642 insertions(+), 33 deletions(-) create mode 100644 docs/guide/currency-handling.md create mode 100755 script/extract-doc-snippets.js diff --git a/.eslintignore b/.eslintignore index 03546876e..bd53198ca 100644 --- a/.eslintignore +++ b/.eslintignore @@ -24,5 +24,6 @@ lib script test-jasmine test-jest +test-utils/snippets typedoc typings diff --git a/.gitignore b/.gitignore index 98181e71c..f63fec173 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ dev*.html .DS_Store /test/hyperformula-tests/ + +# Doc snippets are regenerated from the docs before every test run +# (the `test:*` scripts run `snippets:extract` first), so they are never committed. +/test-utils/snippets/*.generated.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 683e1da14..9ef13cc16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674) +- Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145) +- Added support for LCID-tagged currency format strings (e.g. `[$USD-409] #,##0.00`) in the `TEXT` function. [#1665](https://github.com/handsontable/hyperformula/pull/1665) ## [3.3.0] - 2026-05-20 diff --git a/DEV_DOCS.md b/DEV_DOCS.md index 24461cf36..9ca91f9aa 100644 --- a/DEV_DOCS.md +++ b/DEV_DOCS.md @@ -59,23 +59,6 @@ All spreadsheet functions are implemented as plugins extending `FunctionPlugin`. - uses the `runFunction()` helper for argument validation, coercion, and array handling - registers function translations in `src/i18n/languages/` -## How to add a new function - -Adding a built-in function is similar to adding a [custom function](docs/guide/custom-functions.md), so that guide is a useful reference for the function-implementation patterns (argument metadata, return types, array handling). The built-in flow on top of that is: - -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/`. - -## Code style - -- Prefer a functional approach where possible (`filter`, `map`, `reduce`). -- Write self-documenting code: use meaningful names for classes, functions, and variables. Add code comments only when they explain intent the code itself cannot. -- Add JSDoc to all classes and functions. -- ESLint is the source of truth for formatting and code rules. Run `npm run lint` before submitting changes (see [building](docs/guide/building.md#run-the-linter)). - ## Definition of Done Each change to the production code (bugfix, new feature, or improvement) must include the following elements **before** requesting a code review: @@ -93,6 +76,30 @@ Each change to the production code (bugfix, new feature, or improvement) must in - Changelog entry - Pull request description +A single pull request should contain an atomic self-contained functional change (single bugfix, single feature, single improvement). If a pull request contains multiple features or bugfixes, it should be split. + +## Code style + +- Prefer a functional approach where possible (`filter`, `map`, `reduce`). +- Write self-documenting code: use meaningful names for classes, functions, and variables. Add code comments only when they explain intent the code itself cannot. +- Add JSDoc to all classes and functions. +- ESLint is the source of truth for formatting and code rules. Run `npm run lint` before submitting changes (see [building](docs/guide/building.md#run-the-linter)). + +## Automatic tests + +- All changes to the production code must be covered by automatic tests kept in the `test/` directory. +- Each test case must be very simple and focused on a single assertion. Don't use loops, conditionals, or other control flow statements in test cases. + +## How to add a new function + +Adding a built-in function is similar to adding a [custom function](docs/guide/custom-functions.md), so that guide is a useful reference for the function-implementation patterns (argument metadata, return types, array handling). The built-in flow on top of that is: + +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/`. + ## Internationalization and function translations HyperFormula supports internationalization and provides localized function names for all built-in languages. Translation files live in `src/i18n/languages/`. New functions must include translations for all built-in languages. diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index d185119bc..a4a8bd667 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -255,6 +255,7 @@ module.exports = { ['/guide/i18n-features', 'Internationalization features'], ['/guide/localizing-functions', 'Localizing functions'], ['/guide/date-and-time-handling', 'Date and time handling'], + ['/guide/currency-handling', 'Currency handling'], ] }, { diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 5798f0404..cc535dbcf 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -28,7 +28,7 @@ spreadsheet software. That is because a spreadsheet is probably the most universal software ever created. We wanted the same flexibility for HyperFormula but without the constraints of the spreadsheet UI. -Each of HyperFormula's built-in function names is available in [17 languages](localizing-functions.md#list-of-supported-languages) and [custom language packs](localizing-functions) can be added. +Each of HyperFormula's built-in function names is available in [17 languages](localizing-functions.md#list-of-supported-languages) and [custom language packs](localizing-functions.md) can be added. The latest version of HyperFormula has an extensive collection of **{{ $page.functionsCount }}** functions grouped into categories: @@ -50,7 +50,7 @@ The latest version of HyperFormula has an extensive collection of _Some categories such as compatibility and cube are yet to be supported._ ::: tip -You can modify the built-in functions or create your own, by adding a [custom function](custom-functions). +You can modify the built-in functions or create your own, by adding a [custom function](custom-functions.md). ::: ## List of available functions @@ -531,7 +531,7 @@ Total number of functions: **{{ $page.functionsCount }}** | SPLIT | Divides the provided text using the space character as a separator and returns the substring at the zero-based position specified by the second argument.
`SPLIT("Lorem ipsum", 0) -> "Lorem"`
`SPLIT("Lorem ipsum", 1) -> "ipsum"` | SPLIT(Text, Index) | | SUBSTITUTE | Returns string where occurrences of Old_text are replaced by New_text. Replaces only specific occurrence if last parameter is provided. | SUBSTITUTE(Text, Old_text, New_text, [Occurrence]) | | T | Returns text if given value is text, empty string otherwise. | T(Value) | -| TEXT | Converts a number into text according to a given format.
By default, accepts the same formats that can be passed to the [`dateFormats`](../api/interfaces/configparams.md#dateformats) option, but can be further customized with the [`stringifyDateTime`](../api/interfaces/configparams.md#stringifydatetime) option. | TEXT(Number, Format) | +| TEXT | Converts a number into text according to a given format.
By default, accepts the same formats that can be passed to the [`dateFormats`](../api/interfaces/configparams.md#dateformats) option, but can be further customized with the [`stringifyDateTime`](../api/interfaces/configparams.md#stringifydatetime) and [`stringifyCurrency`](../api/interfaces/configparams.md#stringifycurrency) options. | TEXT(Number, Format) | | TEXTJOIN | Joins text from multiple strings and/or ranges with a delimiter. Supports array/range delimiters that cycle through gaps. When ignore_empty is TRUE, empty strings are skipped. Returns #VALUE! if result exceeds 32,767 characters. | TEXTJOIN(Delimiter, Ignore_empty, Text1, [Text2, ...]) | | TRIM | Strips extra spaces from text. | TRIM("Text") | | UNICHAR | Returns the character created by using provided code point. | UNICHAR(Number) | diff --git a/docs/guide/compatibility-with-google-sheets.md b/docs/guide/compatibility-with-google-sheets.md index 5e98ea3b4..507cc997a 100644 --- a/docs/guide/compatibility-with-google-sheets.md +++ b/docs/guide/compatibility-with-google-sheets.md @@ -87,6 +87,10 @@ Options related to date and time formats: - [`stringifyDateTime()`](../api/interfaces/configparams.md#stringifydatetime) - [`stringifyDuration()`](../api/interfaces/configparams.md#stringifyduration) +### `TEXT` function formats + +Google Sheets' `TEXT` function supports a wide range of date, time, and currency formats. To cover the full range in HyperFormula, supply both [`stringifyDateTime()`](../api/interfaces/configparams.md#stringifydatetime) (for dates and durations) and [`stringifyCurrency()`](../api/interfaces/configparams.md#stringifycurrency) (for currency formats — locale-aware grouping, non-`$` symbols, accounting two-section patterns). See [Currency handling](currency-handling.md) for an `Intl.NumberFormat`-based example. + ## Full configuration This configuration aligns HyperFormula with the default behavior of Google Sheets (set to locale `en-US`), as closely as possible at this development stage (version `{{ $page.version }}`). diff --git a/docs/guide/compatibility-with-microsoft-excel.md b/docs/guide/compatibility-with-microsoft-excel.md index fade7ed96..9afd28a3a 100644 --- a/docs/guide/compatibility-with-microsoft-excel.md +++ b/docs/guide/compatibility-with-microsoft-excel.md @@ -156,6 +156,10 @@ Options related to date and time formats: - [`stringifyDateTime()`](../api/interfaces/configparams.md#stringifydatetime) - [`stringifyDuration()`](../api/interfaces/configparams.md#stringifyduration) +### `TEXT` function formats + +Excel's `TEXT` function supports a wide range of date, time, and currency formats. To cover the full range in HyperFormula, supply both [`stringifyDateTime()`](../api/interfaces/configparams.md#stringifydatetime) (for dates and durations) and [`stringifyCurrency()`](../api/interfaces/configparams.md#stringifycurrency) (for currency formats — locale-aware grouping, non-`$` symbols, accounting two-section patterns). See [Currency handling](currency-handling.md) for an `Intl.NumberFormat`-based example. + ## Full configuration This configuration aligns HyperFormula with the default behavior of Microsoft Excel (set to locale `en-US`), as closely as possible at this development stage (version `{{ $page.version }}`). diff --git a/docs/guide/currency-handling.md b/docs/guide/currency-handling.md new file mode 100644 index 000000000..fb0e8b0ab --- /dev/null +++ b/docs/guide/currency-handling.md @@ -0,0 +1,211 @@ +# Currency handling + +HyperFormula treats currency through **two independent mechanisms**: + +- **Currency input** — recognizing currency literals (e.g. `"100 zł"`) when they appear in cell values, so they become numeric values tagged as currency rather than strings. Controlled by [`currencySymbol`](../api/interfaces/configparams.md#currencysymbol). +- **Currency output** — rendering numbers as currency strings via the `TEXT` function. Simple `$`-prefixed formats work out of the box; richer locale-aware patterns plug in through [`stringifyCurrency`](../api/interfaces/configparams.md#stringifycurrency). + +The two mechanisms are orthogonal — configure both for full coverage. HyperFormula ships with no currency data and no currency-library dependency, so you stay in control of which symbols are recognized and how they render. + +## Currency input + +By default, HyperFormula recognizes `$` as a currency symbol in cell input. To add more (for example Polish złoty), pass an array of recognized symbols to [`currencySymbol`](../api/interfaces/configparams.md#currencysymbol): + +```javascript +const hf = HyperFormula.buildFromArray( + [['100 zł', '=A1 * 1.23']], + { currencySymbol: ['$', 'zł'] } +); + +console.log(hf.getCellValue({ sheet: 0, col: 0, row: 0 })); // 100 +console.log(hf.getCellValueDetailedType({ sheet: 0, col: 0, row: 0 })); // 'NUMBER_CURRENCY' +console.log(hf.getCellValue({ sheet: 0, col: 1, row: 0 })); // 123 +``` + +Notes: + +- The symbol can appear as a **prefix** (`"$100"`) or as a **suffix** (`"100 zł"`). Both forms are recognized. +- Each entry in `currencySymbol` is a literal string — no regular expressions. To support multiple locales, list every symbol you want recognized. +- Detected literals are exposed as numeric values; the currency tag is available via [`getCellValueDetailedType()`](../api/classes/hyperformula.md#getcellvaluedetailedtype) as `NUMBER_CURRENCY`. + +`currencySymbol` controls **only** how HyperFormula parses input. It does not influence what the `TEXT` function returns — that is governed by the format string and the [`stringifyCurrency`](#currency-output) callback described below. + +## Currency output + +The `TEXT` function renders a number with a format string. HyperFormula's built-in number formatter handles the simplest currency-shaped patterns out of the box; richer patterns need a [`stringifyCurrency`](../api/interfaces/configparams.md#stringifycurrency) callback. + +### Default behavior + +With no `stringifyCurrency` configured, the built-in formatter handles simple `$`-prefixed formats — `"$0.00"`, `"$0"`, and `"$#.00"`: + +```javascript +const hf = HyperFormula.buildFromArray([ + [1234.5, '=TEXT(A1, "$0.00")'], + [1234.5, '=TEXT(A2, "$#.00")'], +]); + +console.log(hf.getCellValue({ sheet: 0, col: 1, row: 0 })); // "$1234.50" +console.log(hf.getCellValue({ sheet: 0, col: 1, row: 1 })); // "$1234.50" +``` + +A non-`$` symbol used purely as a suffix (no thousands grouping, no decimal-comma) also passes through unchanged: + +```javascript +const hf = HyperFormula.buildFromArray([[1234.5, '=TEXT(A1, "0.00 zł")']]); +console.log(hf.getCellValue({ sheet: 0, col: 1, row: 0 })); // "1234.50 zł" +``` + +Configure `stringifyCurrency` when your formula corpus needs more advanced currency formats. E.g.: + +- thousands grouping (`"$#,##0.00"`), +- non-`$` symbols with grouping (`"[$€-2] #,##0.00"`, `"[$zł-415] #,##0.00"`), +- locale-specific decimal separators (e.g. the Polish `"1234,50 zł"` pattern — the built-in formatter always emits `.` as the decimal), +- accounting two-section formats (`"$#,##0.00;($#,##0.00)"`). + +### Custom currency formatting + +The callback contract: + +```ts +stringifyCurrency: (value: number, currencyFormat: string) => string | undefined +``` + +The function receives the raw number and the format string passed to `TEXT`. Return a formatted string to override the built-in formatter, or `undefined` to fall through to it. + +#### Minimal example + +```javascript +// Recognize "$..."-prefixed formats and ignore the rest: +const stringifyCurrency = (value, fmt) => + fmt.startsWith('$') ? `$${value.toFixed(2)}` : undefined; + +const hf = HyperFormula.buildFromArray([ + [1234.5, '=TEXT(A1, "$#,##0.00")'], +], { stringifyCurrency }); + +console.log(hf.getCellValue({ sheet: 0, col: 1, row: 0 })); // "$1234.50" +``` + +This callback handles `$`-prefixed formats and falls through (returns `undefined`) for everything else. For any format the callback opts out of, HyperFormula proceeds to the next handler in the dispatch chain: the default date / duration formatters, then the built-in number formatter, and finally the raw format string if nothing matched. + +#### Reference table + +Side-by-side comparison of the default formatter and the docs adapter from the section below: + +| Format | Without callback | With adapter callback (section below) | +|---|---|---| +| `"$0.00"` | `"$1234.50"` | `"$1234.50"` | +| `"$#.00"` | `"$1234.50"` | `"$1234.50"` | +| `"$#,##0.00"` | `"$1235,##0.00"` (no grouping) | `"$1,234.50"` | +| `"[$€-2] #,##0.00"` | `"[$€-2] 1235,##0.00"` (no grouping) | `"1.234,50 €"` | +| `"$#,##0.00;($#,##0.00)"` (value `-1234.5`) | `"$-1235,##0.00;($#,##0.00)"` (no grouping) | `"($1,234.50)"` | + +#### Error behavior + +If your callback throws, HyperFormula propagates the exception. Wrap your formatter in `try/catch` if it can fail, and return `undefined` as the opt-out signal for unsupported formats — throwing is reserved for unexpected errors. + +#### Example: `Intl.NumberFormat` adapter (zero dependencies) + +This adapter handles a representative subset of popular currency format strings using native [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat). Extend the `LCID_TO_LOCALE` map to cover more locales — see the [MS-LCID](https://learn.microsoft.com/openspecs/windows_protocols/ms-lcid) specification for canonical identifiers. + + +```javascript +// Extend the LCID_TO_LOCALE map and CURRENCY_RULES list to cover more formats. + +const LCID_TO_LOCALE = { + '-409': { locale: 'en-US', currency: 'USD' }, // USD + '-2': { locale: 'de-DE', currency: 'EUR' }, // EUR (generic) + '-411': { locale: 'ja-JP', currency: 'JPY' }, // JPY + '-415': { locale: 'pl-PL', currency: 'PLN' }, // PLN + '-809': { locale: 'en-GB', currency: 'GBP' }, // GBP +} + +const CURRENCY_RULES = [ + // [$SYMBOL-LCID] #,##0[.00] — locale-tagged currency format. + // SYMBOL portion requires at least one character (`+`, not `*`) so that + // locale-only modifiers like `[$-409]` (used on date/time formats) are + // NOT misclassified as currency by this adapter. + { + pattern: /^\[\$([^\-\]]+)-([0-9A-Fa-f]+)\]\s*#,##0(\.0+)?$/, + build: (match) => { + const lcid = '-' + match[2] + const fractionDigits = (match[3] || '.').length - 1 + const entry = LCID_TO_LOCALE[lcid] || { locale: 'en-US', currency: 'USD' } + return new Intl.NumberFormat(entry.locale, { + style: 'currency', + currency: entry.currency, + minimumFractionDigits: fractionDigits, + maximumFractionDigits: fractionDigits, + }) + }, + }, + // $#,##0.00 — USD shorthand + { + pattern: /^\$#,##0(\.0+)?$/, + build: (match) => new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: (match[1] || '.').length - 1, + maximumFractionDigits: (match[1] || '.').length - 1, + }), + }, +] + +// Accounting: $#,##0.00;($#,##0.00) — positive;negative with parentheses. +// Note: when both sections are plain (e.g. `$#,##0.00;$#,##0.00`), the adapter +// honors the negative section AS-IS without auto-prepending `-` — the +// format author explicitly opted out of automatic sign. +function tryAccountingFormat(value, format) { + const sections = format.split(';') + if (sections.length !== 2) return undefined + const isNegative = value < 0 + const section = sections[isNegative ? 1 : 0] + const parenMatch = /^\(\$#,##0(\.0+)?\)$/.exec(section) + const plainMatch = /^\$#,##0(\.0+)?$/.exec(section) + if (!parenMatch && !plainMatch) return undefined + const fractionDigits = ((parenMatch || plainMatch)[1] || '.').length - 1 + const nf = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + minimumFractionDigits: fractionDigits, + maximumFractionDigits: fractionDigits, + }) + const formatted = nf.format(Math.abs(value)) + return isNegative && parenMatch ? `(${formatted})` : formatted +} + +export const customStringifyCurrency = (value, currencyFormat) => { + if (typeof currencyFormat !== 'string') return undefined + const accounting = tryAccountingFormat(value, currencyFormat) + if (accounting !== undefined) return accounting + + for (const rule of CURRENCY_RULES) { + const match = rule.pattern.exec(currencyFormat) + if (match) return rule.build(match).format(value) + } + // Not a recognized currency format — let HyperFormula fall through + // to the built-in number formatter. + return undefined +} +``` + + +#### Limitations of the reference adapter + +- **It uses each currency's CLDR locale conventions, not your HyperFormula config.** Output is produced by `Intl.NumberFormat`, so the thousands grouping and decimal separator come from the currency's locale (e.g. `pl-PL` → `1 234,50 zł`, `de-DE` → `1.234,50 €`). The adapter does **not** read HyperFormula's [`decimalSeparator`](../api/interfaces/configparams.md#decimalseparator) / [`thousandSeparator`](../api/interfaces/configparams.md#thousandseparator) config. +- **It recognizes only a representative subset of format shapes.** It handles LCID-tagged formats (`[$SYM-LCID] #,##0.00`), `$`-shorthand (`$#,##0.00`), and simple two-section accounting (`$#,##0.00;($#,##0.00)`). For any other shape it returns `undefined`, so HyperFormula falls through to its built-in number formatter — which cannot expand `#,##0` thousands grouping nor interpret multi-section `;` formats, producing incorrect output for those patterns. + +If you need more complex currency formatting such as: +- Arbitrary Excel-style format strings, +- Precision-safe arithmetic on currency values (e.g. cents as integers), +- ISO 4217 currency metadata for dozens of currencies, + +consider wrapping a specialized currency formatter library such as [`Dinero.js` v2](https://v2.dinerojs.com/) inside the callback. The contract is the same: `(value: number, currencyFormat: string) => string | undefined`. Return `undefined` for any format string you don't want to handle and HyperFormula will fall back to its built-in number formatter. + +#### What is an LCID tag? + +[Microsoft Locale Identifier](https://learn.microsoft.com/openspecs/windows_protocols/ms-lcid) (LCID) adds locale context to a currency format. The syntax is `[$SYMBOL-LCID]` followed by the number template — for example `[$zł-415] #,##0.00` means *"Polish złoty, hex LCID `415` = `pl-PL`"*, and `[$€-2] #,##0.00` means *"euro, generic"*. The adapter above parses the LCID to pick the matching `Intl.NumberFormat` locale and ISO 4217 currency code. + +## Related configuration + +- [`stringifyDateTime`](../api/interfaces/configparams.md#stringifydatetime) / [`stringifyDuration`](../api/interfaces/configparams.md#stringifyduration) — sister callbacks for date and duration formatting. Combine with `stringifyCurrency` when your formulas mix date/time and currency formats. diff --git a/docs/guide/date-and-time-handling.md b/docs/guide/date-and-time-handling.md index 752d50170..cb0ee3b0a 100644 --- a/docs/guide/date-and-time-handling.md +++ b/docs/guide/date-and-time-handling.md @@ -96,6 +96,8 @@ const data = [["31st Jan 00", "2nd Jun 01", "=B1-A1"]]; And now, HyperFormula recognizes these values as valid dates and can operate on them. +For currency formatting in the `TEXT` function (locale-aware grouping, non-`$` symbols, accounting patterns), see the [Currency handling](currency-handling.md) guide. + ## Demo ::: example #example1 --html 1 --css 2 --js 3 --ts 4 diff --git a/docs/guide/i18n-features.md b/docs/guide/i18n-features.md index 14bb7b3a0..f5c08280b 100644 --- a/docs/guide/i18n-features.md +++ b/docs/guide/i18n-features.md @@ -48,13 +48,7 @@ thousandSeparator: ',', ## Currency symbol -To match your users' currency, you can configure multiple currency symbols ([`currencySymbol`](../api/interfaces/configparams.md#currencysymbol)). - -The default currency symbol is `$`. To add `USD` as an alternative, set: - -```js -currencySymbol: ['$', 'USD'], -``` +To match your users' currency, configure recognized currency symbols and (optionally) custom `TEXT` output formatting. Both sides are covered in the dedicated [Currency handling](currency-handling.md) guide. ## String comparison rules diff --git a/docs/guide/known-limitations.md b/docs/guide/known-limitations.md index e58523372..c4a1bbc61 100644 --- a/docs/guide/known-limitations.md +++ b/docs/guide/known-limitations.md @@ -36,6 +36,7 @@ a circular reference. * The INDEX function doesn't support returning whole rows or columns of the source range – it always returns the contents of a single cell. * The FILTER function accepts either single rows of equal width or single columns of equal height. In other words, all arrays passed to the FILTER function must have equal dimensions, and at least one of those dimensions must be 1. * Array-producing functions (e.g., SEQUENCE, FILTER) require their output dimensions to be determinable at parse time. Passing cell references or formulas as dimension arguments (e.g., `=SEQUENCE(A1)`) results in a `#VALUE!` error, because the output size cannot be resolved before evaluation. +* The TEXT function does not accept embedded double-quote literals in the format string. In Excel, `""` inside a format string is an escape sequence for a literal `"` character — e.g. `=TEXT(1234.5, "#,##0.00 ""zł""")` returns `"1,234.50 zł"`. If your application requires this escape sequence, supply a custom [`stringifyCurrency`](currency-handling.md) callback. ### OFFSET function diff --git a/docs/guide/list-of-differences.md b/docs/guide/list-of-differences.md index 2ba4e9479..aa0e684fe 100644 --- a/docs/guide/list-of-differences.md +++ b/docs/guide/list-of-differences.md @@ -34,7 +34,7 @@ See a full list of differences between HyperFormula, Microsoft Excel, and Google | Applying a scalar value to a function taking range | COLUMNS(A1) | `CellRangeExpected` error. | Treats the element as length-1 range. Returns 1 for the example. | Same as Google Sheets. | | Coercion of explicit arguments | VARP(2, 3, 4, TRUE(), FALSE(), "1",) | 1.9592, based on the behavior of Microsoft Excel. | GoogleSheets implementation is not consistent with the standard (see also `VAR.S`, `STDEV.P`, and `STDEV.S` function.) | 1.9592 | | Ranges created with `:` | A1:A2

A$1:$A$2

A:C

1:2

Sheet1!A1:A2 | Allowed ranges consist of two addresses (A1:B5), columns (A:C) or rows (3:5).
They cannot be mixed or contain named expressions. | Everything allowed. | Same as Google Sheets. | -| Formatting inside the TEXT function | TEXT(A1,"dd-mm-yy")

TEXT(A1,"###.###”) | Not all formatting options are supported,
e.g., only some date formatting options: (`hh`, `mm`, `ss`, `am`, `pm`, `a`, `p`, `dd`, `yy`, and `yyyy`).

No currency formatting inside the TEXT function. | A wide variety of options for string formatting is supported. | Same as Google Sheets. | +| Formatting inside the TEXT function | TEXT(A1,"dd-mm-yy")

TEXT(A1,"###.###”) | To support all date, time and currency formats, set [`stringifyDateTime`](compatibility-with-microsoft-excel.md#date-and-time-formats) and [`stringifyCurrency`](currency-handling.md) configuration options. | A wide variety of options for string formatting is supported. | Same as Google Sheets. | | Cell references inside inline arrays | ={A1, A2} | The array's value is calculated but not updated when the cells' values change. | The array's value is calculated and updated when the cells' values change. | ERROR: invalid array | | SPLIT function | =SPLIT("Lorem ipsum dolor", 0) | This function works differently from Google Sheets version but should be sufficient to achieve the same functionality in most scenarios. Read SPLIT function description on [the Built-in Functions page](built-in-functions.md#text). | Different syntax and return value. | No such function. | | DATEVALUE function | =DATEVALUE("25/02/1991") | Type of the returned value: `CellValueDetailedType.NUMBER_DATE` (compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard) | Cell auto-formatted as **regular number** | Cell auto-formatted as **regular number** | diff --git a/package.json b/package.json index e2c8d29d1..cd9767e34 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "docs:code-examples:generate-js": "bash docs/code-examples-generator.sh", "docs:code-examples:generate-all-js": "bash docs/code-examples-generator.sh --generateAll", "docs:code-examples:format-all-ts": "bash docs/code-examples-generator.sh --formatAllTsExamples", + "snippets:extract": "node script/extract-doc-snippets.js", "bundle-all": "cross-env HF_COMPILE=1 npm-run-all clean compile bundle:** verify-bundles", "bundle:es": "(node script/if-ne-env.js HF_COMPILE=1 || npm run compile) && cross-env-shell BABEL_ENV=es env-cmd -f ht.config.js babel lib --out-file-extension .mjs --out-dir es", "bundle:cjs": "(node script/if-ne-env.js HF_COMPILE=1 || npm run compile) && cross-env-shell BABEL_ENV=commonjs env-cmd -f ht.config.js babel lib --out-dir commonjs", @@ -77,15 +78,15 @@ "verify:typings": "tsc --noEmit", "test": "npm-run-all lint test:jest test:browser", "test:setup-private": "bash test/fetch-tests.sh", - "test:jest": "cross-env NODE_ICU_DATA=node_modules/full-icu jest", + "test:jest": "npm run snippets:extract && cross-env NODE_ICU_DATA=node_modules/full-icu jest", "test:watch": "npm run test:jest -- --watch", "test:tmp": "npm run test:jest -- --watch function-irr", "test:coverage": "npm run test:jest -- --coverage", "test:logMemory": "npm run test:jest -- --runInBand --logHeapUsage", "test:performance": "npm run benchmark:basic && npm run benchmark:cruds", "test:compatibility": "bash test/compatibility/test-compatibility.sh", - "test:ci": "cross-env NODE_ICU_DATA=node_modules/full-icu node --expose-gc ./node_modules/jest/bin/jest --forceExit", - "test:browser": "cross-env-shell BABEL_ENV=dist env-cmd -f ht.config.js karma start", + "test:ci": "npm run snippets:extract && cross-env NODE_ICU_DATA=node_modules/full-icu node --expose-gc ./node_modules/jest/bin/jest --forceExit", + "test:browser": "npm run snippets:extract && cross-env-shell BABEL_ENV=dist env-cmd -f ht.config.js karma start", "test:browser.debug": "cross-env-shell BABEL_ENV=dist NODE_ENV=debug env-cmd -f ht.config.js karma start", "typedoc:build-api": "cross-env NODE_OPTIONS=--openssl-legacy-provider typedoc --options .typedoc.md.ts", "benchmark:basic": "npm run tsnode test/hyperformula-tests/performance/run-basic-benchmark.ts", diff --git a/script/extract-doc-snippets.js b/script/extract-doc-snippets.js new file mode 100755 index 000000000..3fc85f34f --- /dev/null +++ b/script/extract-doc-snippets.js @@ -0,0 +1,257 @@ +#!/usr/bin/env node +/** + * extract-doc-snippets.js — extract documented code snippets so tests can + * import the same source-of-truth the docs publish. + * + * Walks `docs/**\/*.md`. For every snippet block of the form + * + * + * ``` + * // code … + * ``` + * + * + * writes the code to `test-utils/snippets/.generated.ts` with a header + * banner naming the source file. Tests then `import { … }` from the + * generated file instead of re-defining the snippet inline. The generated + * files are NOT committed: they are regenerated from the docs before every + * test run by the `test:*` scripts in package.json (each runs `snippets:extract` + * first), which keeps the docs as the single source of truth and avoids + * duplicating the snippet code in the repository. + * + * **Generated content vs docs source.** The generated `.ts` is functionally + * equivalent to the docs snippet but NOT byte-identical: `stripBlockComments` + * removes lines whose entire content is a `//` comment (e.g. editorial + * section dividers) before writing. Trailing `// comment` after live code is + * preserved. The docs page keeps the educational comments for human readers; + * the generated artifact keeps only the runnable surface for the import + * consumer. + * + * The script intentionally has zero npm deps so it runs in the same Node we + * use for `compile` without adding to package.json. + * + * Constraints: + * - Snippet bodies must NOT contain nested triple-backtick fences. The + * closing fence matcher accepts any `^```\s*$` line, so a nested fenced + * block inside a snippet would terminate the outer match early. + * - Symlinks under `docs/` are not followed (loop / sandbox-escape guard). + * - Recursion depth is capped at MAX_DEPTH to prevent runaway walks. + * - File enumeration order is stabilised via sort() so generated content + * is byte-identical across platforms (CI-determinism). + * + * Exit codes: + * 0 — snippets extracted successfully (or no markers present) + * 1 — any structural error: malformed marker, mismatched markers, + * duplicate name, fence opened-but-not-closed, marker mismatch, + * or missing docs dir + */ +'use strict' + +const fs = require('fs') +const path = require('path') + +const REPO_ROOT = path.resolve(__dirname, '..') +const DOCS_DIR = path.join(REPO_ROOT, 'docs') +const OUT_DIR = path.join(REPO_ROOT, 'test-utils', 'snippets') +const MAX_DEPTH = 8 + +/** + * Recursively list every `.md` file under `dir`. Skips symlinks and bails + * past MAX_DEPTH so a stray loop under `docs/` can't hang the build. + * Entries are sorted at every level for deterministic ordering across + * platforms / filesystems. + */ +function listMarkdown(dir, depth = 0) { + if (depth > MAX_DEPTH) { + console.error(`extract-doc-snippets: depth limit (${MAX_DEPTH}) exceeded under ${dir} — refusing to recurse further`) + return [] + } + const entries = fs.readdirSync(dir, { withFileTypes: true }) + .filter((e) => !e.isSymbolicLink()) // never follow symlinks + .sort((a, b) => a.name.localeCompare(b.name)) + const out = [] + for (const entry of entries) { + const p = path.join(dir, entry.name) + if (entry.isDirectory()) out.push(...listMarkdown(p, depth + 1)) + else if (entry.isFile() && entry.name.endsWith('.md')) out.push(p) + } + return out +} + +/** Throw with a precise diagnostic when a line looks like a snippet marker + * but doesn't match the strict grammar (open or close). Catches typos such + * as `` (no spaces) which the strict regex skips silently. */ +const malformedSniffRe = /\s*$/ + const closeRe = /^\s*$/ + const fenceRe = /^```([a-zA-Z0-9]*)\s*$/ + + let i = 0 + while (i < lines.length) { + const line = lines[i] + const openMatch = openRe.exec(line) + if (!openMatch) { + // Distinguish "not a marker at all" from "looks like a malformed marker". + if (malformedSniffRe.test(line) && !closeRe.exec(line)) { + throw new Error( + `${filePath}:${i + 1} — line looks like a snippet marker but doesn't match the grammar. ` + + `Required form: \`\` (note the spaces around the marker body). ` + + `Got: \`${line.trim()}\``, + ) + } + i++ + continue + } + + const name = openMatch[1] + const startLine = i + 1 + let j = i + 1 + // Skip blank lines between marker and fence. + while (j < lines.length && lines[j].trim() === '') j++ + const fenceOpen = fenceRe.exec(lines[j]) + if (!fenceOpen) { + throw new Error(`${filePath}:${startLine} — snippet:${name} marker not followed by a fenced code block`) + } + const lang = fenceOpen[1] || 'ts' + const codeStart = j + 1 + let k = codeStart + while (k < lines.length && !/^```\s*$/.test(lines[k])) k++ + if (k >= lines.length) { + throw new Error(`${filePath}:${startLine} — snippet:${name} fence opened but never closed`) + } + // Find closing snippet marker after the fence close. + let m = k + 1 + while (m < lines.length && lines[m].trim() === '') m++ + const closeMatch = m < lines.length ? closeRe.exec(lines[m]) : null + if (!closeMatch) { + throw new Error(`${filePath}:${startLine} — snippet:${name} missing closing after fence`) + } + if (closeMatch[1] !== name) { + throw new Error(`${filePath}:${m + 1} — close marker /snippet:${closeMatch[1]} does not match open snippet:${name}`) + } + + yield { + name, + lang, + code: lines.slice(codeStart, k).join('\n'), + sourceFile: path.relative(REPO_ROOT, filePath), + line: startLine, + } + i = m + 1 + } +} + +/** + * Strip pure-comment lines from the snippet body. + * + * Editorial `//` comments in the docs snippet (e.g. `// EUR (generic)`, + * `// $#,##0.00 — USD shorthand`) are useful in the published page where + * a reader is studying the code, but they add noise in the generated test + * artifact where a downstream `import` consumer only cares about the + * functional code. We strip lines that contain ONLY whitespace + `// + * comment` (block-level). Trailing `// comment` after live code is left + * alone — that case needs a JS-aware tokenizer to avoid clobbering URL + * literals like `'https://…'`, and the noise reduction from block-level + * stripping alone is already significant. Collapses runs of resulting + * blank lines to a single blank for readability. + */ +function stripBlockComments(code) { + const lines = code.split('\n') + const blockCommentRe = /^\s*\/\/.*$/ + const kept = lines.filter((ln) => !blockCommentRe.test(ln)) + // Collapse 2+ consecutive blank lines into 1 — keeps section breaks but + // avoids the "every comment was here" gaps the strip would otherwise leave. + const out = [] + let prevBlank = false + for (const ln of kept) { + const isBlank = ln.trim() === '' + if (isBlank && prevBlank) continue + out.push(ln) + prevBlank = isBlank + } + return out.join('\n') +} + +/** Render the generated file with a stable header banner. */ +function render({ name, lang, code, sourceFile, line }) { + const banner = [ + '// Auto-generated by script/extract-doc-snippets.js — DO NOT EDIT.', + `// Source: ${sourceFile}:${line} (snippet:${name})`, + '// Edit the source markdown — this file is regenerated before every test', + '// run (the `test:*` scripts run `snippets:extract`) and is not committed.', + '', + // Docs snippets are written in JS without TypeScript annotations (they + // need to copy-paste runnable for the reader). The .ts extension lets + // consumers `import { … }` without a tsconfig.allowJs change, but the + // body is intentionally untyped — `@ts-nocheck` keeps tsc quiet without + // forcing every snippet author to learn TypeScript. + '// @ts-nocheck', + '', + ].join('\n') + const body = stripBlockComments(code) + return banner + body + (body.endsWith('\n') ? '' : '\n') +} + +/** Remove `*.generated.ts` files in OUT_DIR that aren't in `keep`. */ +function pruneOrphans(keep) { + if (!fs.existsSync(OUT_DIR)) return [] + const removed = [] + for (const name of fs.readdirSync(OUT_DIR).sort()) { + if (!name.endsWith('.generated.ts')) continue + if (keep.has(name)) continue + fs.unlinkSync(path.join(OUT_DIR, name)) + removed.push(path.relative(REPO_ROOT, path.join(OUT_DIR, name))) + } + return removed +} + +function main() { + if (!fs.existsSync(DOCS_DIR)) { + console.error(`extract-doc-snippets: docs dir not found at ${DOCS_DIR}`) + process.exit(1) + } + fs.mkdirSync(OUT_DIR, { recursive: true }) + + const seen = new Map() + const written = [] + const keepBasenames = new Set() + for (const file of listMarkdown(DOCS_DIR)) { + for (const snippet of extractSnippets(file)) { + if (seen.has(snippet.name)) { + const prev = seen.get(snippet.name) + console.error(`extract-doc-snippets: duplicate snippet name "${snippet.name}"`) + console.error(` first: ${prev.sourceFile}:${prev.line}`) + console.error(` second: ${snippet.sourceFile}:${snippet.line}`) + process.exit(1) + } + seen.set(snippet.name, snippet) + const basename = `${snippet.name}.generated.ts` + const outPath = path.join(OUT_DIR, basename) + fs.writeFileSync(outPath, render(snippet)) + written.push(path.relative(REPO_ROOT, outPath)) + keepBasenames.add(basename) + } + } + + const removed = pruneOrphans(keepBasenames) + + if (written.length === 0 && removed.length === 0) { + console.log('extract-doc-snippets: no blocks found') + return + } + if (written.length > 0) { + console.log(`extract-doc-snippets: wrote ${written.length} file(s)`) + for (const w of written) console.log(` ${w}`) + } + if (removed.length > 0) { + console.log(`extract-doc-snippets: pruned ${removed.length} orphan(s)`) + for (const r of removed) console.log(` - ${r}`) + } +} + +main() diff --git a/src/Config.ts b/src/Config.ts index d47323384..8e8a924ee 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -15,7 +15,7 @@ import {defaultParseToDateTime} from './DateTimeDefault' import {DateTime, instanceOfSimpleDate, SimpleDate, SimpleDateTime, SimpleTime} from './DateTimeHelper' import {AlwaysDense, ChooseAddressMapping} from './DependencyGraph/AddressMapping/ChooseAddressMappingPolicy' import {ConfigValueEmpty, ExpectedValueOfTypeError} from './errors' -import {defaultStringifyDateTime, defaultStringifyDuration} from './format/format' +import {defaultStringifyCurrency, defaultStringifyDateTime, defaultStringifyDuration} from './format/format' import {checkLicenseKeyValidity, LicenseKeyValidityState} from './helpers/licenseKeyValidator' import {HyperFormula} from './HyperFormula' import {TranslationPackage} from './i18n' @@ -59,6 +59,7 @@ export class Config implements ConfigParams, ParserConfig { smartRounding: true, stringifyDateTime: defaultStringifyDateTime, stringifyDuration: defaultStringifyDuration, + stringifyCurrency: defaultStringifyCurrency, timeFormats: ['hh:mm', 'hh:mm:ss.sss'], thousandSeparator: '', undoLimit: 20, @@ -120,6 +121,8 @@ export class Config implements ConfigParams, ParserConfig { /** @inheritDoc */ public readonly stringifyDuration: (time: SimpleTime, formatArg: string) => Maybe /** @inheritDoc */ + public readonly stringifyCurrency: (value: number, currencyFormat: string) => Maybe + /** @inheritDoc */ public readonly precisionEpsilon: number /** @inheritDoc */ public readonly precisionRounding: number @@ -195,6 +198,7 @@ export class Config implements ConfigParams, ParserConfig { precisionRounding, stringifyDateTime, stringifyDuration, + stringifyCurrency, smartRounding, timeFormats, thousandSeparator, @@ -243,6 +247,7 @@ export class Config implements ConfigParams, ParserConfig { this.parseDateTime = configValueFromParam(parseDateTime, 'function', 'parseDateTime') this.stringifyDateTime = configValueFromParam(stringifyDateTime, 'function', 'stringifyDateTime') this.stringifyDuration = configValueFromParam(stringifyDuration, 'function', 'stringifyDuration') + this.stringifyCurrency = configValueFromParam(stringifyCurrency, 'function', 'stringifyCurrency') this.translationPackage = HyperFormula.getLanguage(this.language) this.errorMapping = this.translationPackage.buildErrorMapping() this.nullDate = configValueFromParamCheck(nullDate, instanceOfSimpleDate, 'IDate', 'nullDate') diff --git a/src/ConfigParams.ts b/src/ConfigParams.ts index ad7344a3b..71aeb0bb7 100644 --- a/src/ConfigParams.ts +++ b/src/ConfigParams.ts @@ -310,6 +310,22 @@ export interface ConfigParams { * @category Date and Time */ stringifyDuration: (time: SimpleTime, timeFormat: string) => Maybe, + /** + * Sets a function that converts numeric values into currency-formatted strings. + * + * The function receives the raw value and the format string passed to `TEXT` + * and should return a string or `undefined`. The formatter calls this for + * every format string that reaches it, not only currency-shaped ones — return + * `undefined` for any format your callback does not handle and HyperFormula + * will fall through to the built-in number formatter. + * + * For more information, see the [Currency handling guide](/guide/currency-handling.md). + * + * @default defaultStringifyCurrency + * + * @category Number + */ + stringifyCurrency: (value: number, currencyFormat: string) => Maybe, /** * When set to `false`, no rounding happens, and numbers are equal if and only if they are of truly identical value. * diff --git a/src/format/format.ts b/src/format/format.ts index e605209f5..52e119254 100644 --- a/src/format/format.ts +++ b/src/format/format.ts @@ -10,7 +10,36 @@ import {RawScalarValue} from '../interpreter/InterpreterValue' import {Maybe} from '../Maybe' import {FormatToken, parseForDateTimeFormat, parseForNumberFormat, TokenType} from './parser' +/** + * Detects Excel LCID-tagged currency tags (`[$SYMBOL-LCID]` with a non-empty + * SYMBOL portion). Shared by `defaultStringifyDateTime` and + * `defaultStringifyDuration` so a format string carrying such a tag short- + * circuits both date and duration dispatch and falls through to the + * number formatter (or the user-supplied `stringifyCurrency` callback). + * + * The pattern is intentionally unanchored: any occurrence of `[$SYMBOL-` + * in the format string triggers the guard. Excel does not mix date/time + * tokens with a currency tag in the same format string, so a mid-string + * match cannot misclassify a legitimate composite — every observed + * format string with a currency tag is currency-only. + */ +const LCID_CURRENCY_TAG = /\[\$[^\-\]]+-/ + export function format(value: number, formatArg: string, config: Config, dateHelper: DateTimeHelper): RawScalarValue { + // Currency callback runs first so a user-supplied stringifyCurrency can + // intercept LCID-tagged or bare-letter currency formats before the + // date/time parser greedily consumes characters like 'D', 'M', 'S', 'Y' + // (e.g. '[$USD-409] #,##0.00' would otherwise become '[$US9-409] #,##0.00'). + // The default callback returns undefined for every input. For non-currency + // formats (dates, durations, $#,##0.00, etc.) this preserves the existing + // dispatch path bit-for-bit. For LCID-tagged currency formats (`[$SYMBOL-LCID] ...`) + // the LCID guards in defaultStringifyDateTime/Duration also short-circuit, + // so the value falls through to parseForNumberFormat — a deliberate change + // versus pre-HF-24 behavior, where the date parser would mangle the symbol. + const tryCurrency = config.stringifyCurrency(value, formatArg) + if (tryCurrency !== undefined) { + return tryCurrency + } const tryDateTime = config.stringifyDateTime(dateHelper.numberToSimpleDateTime(value), formatArg) // default points to defaultStringifyDateTime() if (tryDateTime !== undefined) { return tryDateTime @@ -80,7 +109,30 @@ function numberFormat(tokens: FormatToken[], value: number): RawScalarValue { return result } +/** + * Default `stringifyDuration` callback — formats a duration value against an + * Excel-style time format string (e.g. `[hh]:mm:ss`). + * + * Returns `undefined` for format strings that are not duration formats so the + * dispatcher in `format()` can fall through to other handlers. + * + * **LCID currency-tag guard** — sibling to the same guard in + * `defaultStringifyDateTime`; explicitly returns `undefined` for Excel + * currency tags `[$SYMBOL-LCID]` because the SYMBOL portion contains + * duration-token letters (`H` in CHF/HUF, `m` in AMD/HMD) that + * `parseForDateTimeFormat` would otherwise interpret as time tokens and + * mangle the output. See `defaultStringifyDateTime` for the full + * symbol-vs-locale-modifier rationale and the historical pre-HF-24 + * behaviour the guard corrects. + * + * @param time parsed duration value to render + * @param formatArg Excel-style format string + * @returns formatted string, or `undefined` to defer to the next dispatch step + */ export function defaultStringifyDuration(time: SimpleTime, formatArg: string): Maybe { + if (LCID_CURRENCY_TAG.test(formatArg)) { + return undefined + } const expression = parseForDateTimeFormat(formatArg) if (expression === undefined) { return undefined @@ -142,7 +194,37 @@ export function defaultStringifyDuration(time: SimpleTime, formatArg: string): M return result } +/** + * Default `stringifyDateTime` callback — formats a date/time value against an + * Excel-style format string (e.g. `YYYY-MM-DD HH:mm:ss`). + * + * Returns `undefined` for format strings that are not date/time formats so the + * dispatcher in `format()` can fall through to `parseForNumberFormat` (or to a + * user-supplied `stringifyCurrency` callback for currency-tagged formats). + * + * **LCID currency-tag guard** — explicitly returns `undefined` for Excel + * currency tags `[$SYMBOL-LCID]` (non-empty SYMBOL portion). Without the + * guard, `parseForDateTimeFormat` greedily consumes letters like `D`/`M`/`S`/`Y`/`H` + * inside the currency code (e.g. `D` in USD, `H` in CHF, `M`+`D` in AMD), + * mangling the output of an `[$USD-409] #,##0.00` format into + * `[$US9-409] #,##0.00` because `D` is read as a day token. The pre-HF-24 + * behaviour was to mis-format; the guarded return is the deliberate + * correction, not a regression. Bit-for-bit compatibility is preserved for + * every non-currency format (dates, durations, `$#,##0.00`, etc.). + * + * The guard pattern (`/\[\$[^\-\]]+-/`) requires ≥1 character between `[$` + * and `-` so it distinguishes currency tags (`[$USD-409]`, `[$€-2]`) from + * Excel's locale-only modifier (`[$-409]`, `[$-F800]`), which is valid on + * date/time formats and must continue to flow through this function. + * + * @param dateTime parsed date/time value to render + * @param formatArg Excel-style format string + * @returns formatted string, or `undefined` to defer to the next dispatch step + */ export function defaultStringifyDateTime(dateTime: SimpleDateTime, formatArg: string): Maybe { + if (LCID_CURRENCY_TAG.test(formatArg)) { + return undefined + } const expression = parseForDateTimeFormat(formatArg) if (expression === undefined) { return undefined @@ -229,3 +311,20 @@ export function defaultStringifyDateTime(dateTime: SimpleDateTime, formatArg: st return result } + +/** + * Default implementation of the `stringifyCurrency` config option. + * + * Returning `undefined` instructs the formatter to fall through to the + * built-in number formatter, preserving HyperFormula's zero-dependency + * default behavior. Replace this default by setting the + * [`stringifyCurrency`](../../api/interfaces/configparams.md#stringifycurrency) + * config option. + * + * @param _value - the numeric value to format (unused in default). + * @param _formatArg - the format string passed to `TEXT` (unused in default). + * @returns `undefined` — caller should fall through to the built-in formatter. + */ +export function defaultStringifyCurrency(_value: number, _formatArg: string): Maybe { + return undefined +} diff --git a/tsconfig.test.json b/tsconfig.test.json index a40e70517..9c874320b 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -7,7 +7,7 @@ "sourceMap": true }, "extends": "./tsconfig", - "include": ["src", "test"], + "include": ["src", "test", "test-utils"], /* Exclude files that are specific for jest setup */ "exclude": ["test/_setupFiles/jest"] } From 2471a37b8a87c99d4bff800de96d26e5d7c2af13 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Mon, 6 Jul 2026 14:52:27 +0200 Subject: [PATCH 12/28] Fix a non-correct changelog entry --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ef13cc16..a78788346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674) - Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145) -- Added support for LCID-tagged currency format strings (e.g. `[$USD-409] #,##0.00`) in the `TEXT` function. [#1665](https://github.com/handsontable/hyperformula/pull/1665) ## [3.3.0] - 2026-05-20 From 4e302ff4348a981f1d2846c7926e70acc1ed896a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20M=C4=99dryga=C5=82?= Date: Mon, 6 Jul 2026 15:59:15 +0200 Subject: [PATCH 13/28] Update share capital in LICENSE.txt (#1700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Update share capital value in LICENSE.txt from PLN 62,800.00 to PLN 67,200.00 [IT-535](https://app.clickup.com/t/9015210959/IT-535) --- > [!NOTE] > **Low Risk** > Documentation-only legal metadata with no effect on application behavior or security. > > **Overview** > Updates the corporate disclosure in **`LICENSE.txt`** so HANDSONCODE’s listed **share capital** reads **PLN 67,200.00** instead of **PLN 62,800.00**; no other license terms or code change. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit c7742081250c777bffdb61b4f37304ae261f8c63. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Kuba Sekowski Co-authored-by: GreenFlux Co-authored-by: Claude Opus 4.6 Co-authored-by: Kuba Sekowski --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index 64a1b72d0..4e317baca 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -3,7 +3,7 @@ Copyright (c) HANDSONCODE sp. z o. o. HYPERFORMULA is a software distributed by HANDSONCODE sp. z o. o., a Polish corporation based in Gdynia, Poland, at Aleja Zwyciestwa 96-98, registered by the District Court in Gdansk under number 538651, -EU VAT: PL5862294002, share capital: PLN 62,800.00. +EU VAT: PL5862294002, share capital: PLN 67,200.00. This software is dual-licensed, giving you the option to use it under either a proprietary license or the GNU General Public License version 3 From d03c3e3f939b7144039364d6c3830ff2507587f1 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Thu, 9 Jul 2026 15:42:10 +0200 Subject: [PATCH 14/28] Add function XIRR (#1701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Implements the Excel `XIRR` function (HF-76). - Adds `XIRR(Values, Dates[, Guess])` to `FinancialPlugin` — returns the internal rate of return for a schedule of cash flows that is not necessarily periodic - Optional `Guess` defaults to `0.1`; solver uses Newton–Raphson with day-based discounting on a 365-day year - Validates aligned value/date ranges (≥2 flows, mixed signs, date ordering), coerces empty value cells to `0`, and returns `#NA!` for single-cell or scalar arguments - Registers `XIRR` in all language packs, updates `built-in-functions.md` and `CHANGELOG.md` - Unit tests added in the private test suite (`function-xirr.spec.ts`) ## Test plan - [x] `npm test` — lint + unit + browser - [x] Microsoft XIRR example, XNPV consistency, range layouts, date edge cases, error paths, guess handling, non-convergence ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [x] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [x] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation --- > [!NOTE] > **Low Risk** > Additive built-in function in FinancialPlugin with localized names and documentation; no changes to auth, persistence, or core engine APIs. > > **Overview** > Adds Excel-compatible **`XIRR(Values, Dates[, Guess])`** for irregular cash-flow schedules, alongside existing **`IRR`** and **`XNPV`**. > > Implementation lives in **`FinancialPlugin`**: aligned value/date ranges are sanitized (empty value cells → `0`, errors propagated, non-numeric → `#VALUE!`), with checks for length match, at least two flows, mixed signs, valid dates, and `Guess > -1`. The rate is solved with **Newton–Raphson** on day-based discounting using a **365-day year**, including overshoot clamping when the iterate would fall at or below **-1**. > > **`XIRR`** is registered in all language packs, documented in **`built-in-functions.md`**, noted in **`CHANGELOG.md`**, and **`AGENTS.md`** gains a reminder to keep PR descriptions up to date. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 36c14650863e1098edf63ecb9067576413577177. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --- AGENTS.md | 2 +- CHANGELOG.md | 1 + docs/guide/built-in-functions.md | 1 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/idID.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/FinancialPlugin.ts | 160 ++++++++++++++++++++++ 21 files changed, 180 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index ee5b0ee7d..99b25536c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ This section is maintained by the team. Whenever an AI agent makes a mistake wor - **Short title** — What the agent did wrong. What it should have done instead. --> -_No items yet._ +1. Often pull request descriptions becomes obsolete. Remember to update it as you work. ## Skills, MCPs, and other agent tools diff --git a/CHANGELOG.md b/CHANGELOG.md index a78788346..113bcb492 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- Added a new function: `XIRR`. [#1701](https://github.com/handsontable/hyperformula/pull/1701) - Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674) - Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145) diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index cc535dbcf..d96f97e78 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -217,6 +217,7 @@ Total number of functions: **{{ $page.functionsCount }}** | TBILLEQ | Returns the bond-equivalent yield for a Treasury bill. | TBILLEQ(Settlement, Maturity, Discount) | | TBILLPRICE | Returns the price per $100 face value for a Treasury bill. | TBILLPRICE(Settlement, Maturity, Discount) | | TBILLYIELD | Returns the yield for a Treasury bill. | TBILLYIELD(Settlement, Maturity, Price) | +| XIRR | Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic. | XIRR(Values, Dates[, Guess]) | | XNPV | Returns net present value. | XNPV(Rate, Payments, Dates) | ### Logical diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 71ce56b14..5a0b8ef00 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'WORKDAY.INTL', XLOOKUP: 'XVYHLEDAT', XNPV: 'XNPV', + XIRR: 'XIRR', XOR: 'XOR', YEAR: 'ROK', YEARFRAC: 'YEARFRAC', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index ea5a1e4ce..39a83b4fc 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'ARBEJDSDAG.INTL', XLOOKUP: 'XOPSLAG', XNPV: 'NETTO.NUTIDSVÆRDI', + XIRR: 'INTERN.RENTE', XOR: 'XELLER', YEAR: 'ÅR', YEARFRAC: 'ÅR.BRØK', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 24a8ec03b..4a3046ce8 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'ARBEITSTAG.INTL', XLOOKUP: 'XVERWEIS', XNPV: 'XKAPITALWERT', + XIRR: 'XINTZINSFUSS', XOR: 'XODER', YEAR: 'JAHR', YEARFRAC: 'BRTEILJAHRE', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index e878f897a..7e9130cd4 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -252,6 +252,7 @@ const dictionary: RawTranslationPackage = { WORKDAY: 'WORKDAY', 'WORKDAY.INTL': 'WORKDAY.INTL', XNPV: 'XNPV', + XIRR: 'XIRR', XOR: 'XOR', XLOOKUP: 'XLOOKUP', YEAR: 'YEAR', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index d36d14fd0..eb57d2d4f 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -251,6 +251,7 @@ export const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'DIA.LAB.INTL', XLOOKUP: 'BUSCARX', XNPV: 'VNA.NO.PER', + XIRR: 'TIR.NO.PER', XOR: 'XOR', YEAR: 'AÑO', YEARFRAC: 'FRAC.AÑO', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 5735278d8..655643e6a 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'TYÖPÄIVÄ.KANSVÄL', XLOOKUP: 'XHAKU', XNPV: 'NNA.JAKSOTON', + XIRR: 'SISÄINEN.KORKO.JAKSOTON', XOR: 'EHDOTON.TAI', YEAR: 'VUOSI', YEARFRAC: 'VUOSI.OSA', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 044c70715..a30d4a118 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'SERIE.JOUR.OUVRE.INTL', XLOOKUP: 'RECHERCHEX', XNPV: 'VAN.PAIEMENTS', + XIRR: 'TRI.PAIEMENTS', XOR: 'OUX', YEAR: 'ANNEE', YEARFRAC: 'FRACTION.ANNEE', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 3a5119222..03b6b9dcf 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'KALK.MUNKANAP.INTL', XLOOKUP: 'XKERES', XNPV: 'XNJÉ', + XIRR: 'XBMR', XOR: 'XVAGY', YEAR: 'ÉV', YEARFRAC: 'TÖRTÉV', diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts index b07d9aad8..0a3f025bc 100644 --- a/src/i18n/languages/idID.ts +++ b/src/i18n/languages/idID.ts @@ -252,6 +252,7 @@ const dictionary: RawTranslationPackage = { WORKDAY: 'HARI.KERJA.SELESAI', 'WORKDAY.INTL': 'HARI.KERJA.SELESAI.INTL', XNPV: 'XNPV', + XIRR: 'XIRR', XOR: 'XATAU', XLOOKUP: 'XLOOKUP', YEAR: 'TAHUN', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 2716d3471..7dee12d8d 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'GIORNO.LAVORATIVO.INTL', XLOOKUP: 'CERCA.X', XNPV: 'VAN.X', + XIRR: 'TIR.X', XOR: 'XOR', YEAR: 'ANNO', YEARFRAC: 'FRAZIONE.ANNO', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 80d9948f6..dcc51c4be 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'ARBEIDSDAG.INTL', XLOOKUP: 'XOPPSLAG', XNPV: 'XNNV', + XIRR: 'XIR', XOR: 'EKSKLUSIVELLER', YEAR: 'ÅR', YEARFRAC: 'ÅRDEL', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 57d6fddb9..118f7b497 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'WERKDAG.INTL', XLOOKUP: 'X.ZOEKEN', XNPV: 'NHW2', + XIRR: 'IR.SCHEMA', XOR: 'EX.OF', YEAR: 'JAAR', YEARFRAC: 'JAAR.DEEL', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 251a35bba..262c2459e 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'DZIEŃ.ROBOCZY.NIESTAND', XLOOKUP: 'X.WYSZUKAJ', XNPV: 'XNPV', + XIRR: 'XIRR', XOR: 'XOR', YEAR: 'ROK', YEARFRAC: 'CZĘŚĆ.ROKU', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 3195612f0..5d048e179 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'DIATRABALHO.INTL', XLOOKUP: 'PROCX', XNPV: 'XVPL', + XIRR: 'XTIR', XOR: 'OUEXCL', YEAR: 'ANO', YEARFRAC: 'FRAÇÃOANO', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index cfbf51e59..c42ef694c 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'РАБДЕНЬ.МЕЖД', XLOOKUP: 'ПРОСМОТРХ', XNPV: 'ЧИСТНЗ', + XIRR: 'ЧИСТВНДОХ', XOR: 'ИСКЛИЛИ', YEAR: 'ГОД', YEARFRAC: 'ДОЛЯГОДА', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index bce2c4cf8..90f9357f2 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'ARBETSDAGAR.INT', XLOOKUP: 'XLETAUPP', XNPV: 'XNUVÄRDE', + XIRR: 'XIRR', XOR: 'XOR', YEAR: 'ÅR', YEARFRAC: 'ÅRDEL', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index af2c25390..6e898ed36 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -251,6 +251,7 @@ const dictionary: RawTranslationPackage = { 'WORKDAY.INTL': 'İŞGÜNÜ.ULUSL', XLOOKUP: 'ÇAPRAZARA', XNPV: 'ANBD', + XIRR: 'AİÇVERİMORANI', XOR: 'ÖZELVEYA', YEAR: 'YIL', YEARFRAC: 'YILORAN', diff --git a/src/interpreter/plugin/FinancialPlugin.ts b/src/interpreter/plugin/FinancialPlugin.ts index 0071f44e7..c1f0c782a 100644 --- a/src/interpreter/plugin/FinancialPlugin.ts +++ b/src/interpreter/plugin/FinancialPlugin.ts @@ -10,6 +10,7 @@ import {InterpreterState} from '../InterpreterState' import { EmptyValue, getRawValue, + InternalScalarValue, InterpreterValue, isExtendedNumber, NumberType, @@ -289,6 +290,15 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp ], returnNumberType: NumberType.NUMBER_PERCENT }, + 'XIRR': { + method: 'xirr', + parameters: [ + {argumentType: FunctionArgumentType.RANGE}, + {argumentType: FunctionArgumentType.RANGE}, + {argumentType: FunctionArgumentType.NUMBER, defaultValue: 0.1}, + ], + returnNumberType: NumberType.NUMBER_PERCENT + }, } public pmt(ast: ProcedureAst, state: InterpreterState): InterpreterValue { @@ -788,6 +798,49 @@ export class FinancialPlugin extends FunctionPlugin implements FunctionPluginTyp } ) } + + /** + * Calculates the internal rate of return for a schedule of cash flows that is not necessarily periodic. + * @param {ProcedureAst} ast - The AST node representing the function call. + * @param {InterpreterState} state - The interpreter state. + * @returns {InterpreterValue} The internal rate of return. + */ + public xirr(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('XIRR'), + (values: SimpleRangeValue, dates: SimpleRangeValue, guess: number) => { + if (guess <= -1) { + return new CellError(ErrorType.NUM) + } + + const cashFlows = sanitizeXirrRange(values.valuesFromTopLeftCorner()) + if (cashFlows instanceof CellError) { + return cashFlows + } + + const paymentDates = sanitizeXirrRange(dates.valuesFromTopLeftCorner()) + if (paymentDates instanceof CellError) { + return paymentDates + } + + if (cashFlows.length !== paymentDates.length) { + return new CellError(ErrorType.NUM, ErrorMessage.EqualLength) + } + + // A schedule needs at least two cash flows to define a rate of return. + if (cashFlows.length < 2) { + return new CellError(ErrorType.NA) + } + + const hasPositive = cashFlows.some(value => value > 0) + const hasNegative = cashFlows.some(value => value < 0) + if (!hasPositive || !hasNegative) { + return new CellError(ErrorType.NUM) + } + + return xirrCore(cashFlows, paymentDates, guess) + } + ) + } } function pmtCore(rate: number, periods: number, present: number, future: number, type: number): number { @@ -897,3 +950,110 @@ function irrCore(values: number[], guess: number): number | CellError { return new CellError(ErrorType.NUM) } + +/** + * Converts raw range values into an array of numbers for XIRR. + * + * Empty cells are treated as zeros, errors are propagated, and any non-numeric + * value (text or logical) results in a #VALUE! error. + * @param {InternalScalarValue[]} rawValues - The raw values taken from a range. + * @returns {number[] | CellError} The numeric values, or a propagated/coercion error. + */ +function sanitizeXirrRange(rawValues: InternalScalarValue[]): number[] | CellError { + const result: number[] = [] + for (const rawValue of rawValues) { + if (rawValue instanceof CellError) { + return rawValue + } else if (rawValue === EmptyValue) { + result.push(0) + } else if (isExtendedNumber(rawValue)) { + result.push(getRawValue(rawValue)) + } else { + return new CellError(ErrorType.VALUE, ErrorMessage.NumberExpected) + } + } + return result +} + +/** + * Calculates XIRR using the Newton-Raphson method. + * + * XIRR is the rate r for which the net present value of the cash flows, discounted + * over the actual number of days between payments (assuming a 365-day year), equals zero: + * sum( values[i] / (1 + r)^((dates[i] - dates[0]) / 365) ) = 0 + * @param {number[]} values - The cash flow amounts. + * @param {number[]} dates - The payment dates as serial numbers, aligned with `values`. + * @param {number} guess - The initial estimate of the rate of return. + * @returns {number | CellError} The internal rate of return, or a #NUM! error. + */ +function xirrCore(values: number[], dates: number[], guess: number): number | CellError { + const epsMax = 1e-10 + const iterMax = 50 + + const startDate = Math.floor(dates[0]) + if (startDate < 0) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } + + const dayOffsets: number[] = [] + for (let i = 0; i < dates.length; i++) { + const truncatedDate = Math.floor(dates[i]) + if (truncatedDate < startDate) { + return new CellError(ErrorType.NUM, ErrorMessage.ValueSmall) + } + dayOffsets.push(truncatedDate - startDate) + } + + let rate = guess + + for (let iter = 0; iter < iterMax; iter++) { + // Calculate XNPV and its derivative at the current rate. + let npv = 0 + let dnpv = 0 + + for (let i = 0; i < values.length; i++) { + const exponent = dayOffsets[i] / 365 + const base = 1 + rate + const factor = Math.pow(base, exponent) + if (!isFinite(factor) || factor === 0) { + return new CellError(ErrorType.NUM) + } + npv += values[i] / factor + dnpv -= exponent * values[i] / (factor * base) + } + + if (!isFinite(npv) || !isFinite(dnpv)) { + return new CellError(ErrorType.NUM) + } + + // Check for convergence. + if (Math.abs(npv) < epsMax) { + return rate + } + + // Check if the derivative is too small (avoid division by zero). + if (Math.abs(dnpv) < epsMax) { + return new CellError(ErrorType.NUM) + } + + // Newton-Raphson step. + let newRate = rate - npv / dnpv + if (!isFinite(newRate)) { + return new CellError(ErrorType.NUM) + } + + // Clamp: when Newton overshoots past -1, bisect between current rate and -1. + if (newRate <= -1) { + newRate = (rate - 1) / 2 + } + + // Check for convergence based on rate change. + if (Math.abs(newRate - rate) < epsMax) { + return newRate + } + + rate = newRate + } + + return new CellError(ErrorType.NUM) +} From 033f8acfd92022ce9a1f13e8a3382c37d94325ec Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Thu, 9 Jul 2026 15:51:30 +0200 Subject: [PATCH 15/28] Add independent security certificate documentation (#1704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Context Document HyperFormula's independent security assessment and certificate awarded by TestArmy Group S.A. in July 2026. This adds credibility to the security standards section and provides transparency about third-party security validation. ### Changes - Updated "Quality" page title to "Quality & Security" to better reflect content scope - Added new "Independent security certificate" subsection documenting: - TestArmy Group S.A. security certificate (signed by CEO Wojciech Humiński) - Assessment details for HyperFormula v3.3.0 - Assessment scope: code review, white-box penetration testing, static/dynamic/manual analysis against OWASP ASVS and Top 10, dependency analysis - Updated sidebar navigation to reflect new page title ### Types of changes - [x] Change to the documentation ### How did you test your changes? Documentation changes are self-evident. Verified: - Markdown syntax is valid - Navigation config matches updated page title - Links to npm package version are correct ### Checklist - [x] My changes require a documentation update. https://claude.ai/code/session_017L6WnY9qHHBmfSE4Qe2ZH7 --- > [!NOTE] > **Low Risk** > Documentation and navigation label changes only; no runtime or library behavior is modified. > > **Overview** > Renames the **Quality** guide page and sidebar entry to **Quality & Security** so the nav matches the security content already on the page. > > Adds an **Independent security certificate** subsection under Security describing the July 2026 TestArmy Group S.A. assessment of HyperFormula v3.3.0 (white-box testing, OWASP ASVS/Top 10–oriented analysis, dependency review) and a link to download `hyperformula_security_certificate.pdf`. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 81f40b220ab3efd5ec121cb95d5ca5c50a3f0c2d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Claude --- docs/.vuepress/config.js | 2 +- .../hyperformula_security_certificate.pdf | Bin 0 -> 409753 bytes docs/guide/quality.md | 14 +++++++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 docs/.vuepress/public/hyperformula_security_certificate.pdf diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index a4a8bd667..8971d8f5b 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -294,7 +294,7 @@ module.exports = { title: 'About', collapsable: false, children: [ - ['/guide/quality', 'Quality'], + ['/guide/quality', 'Quality & Security'], ['/guide/supported-browsers', 'Supported browsers'], ['/guide/dependencies', 'Dependencies'], ['/guide/licensing', 'Licensing'], diff --git a/docs/.vuepress/public/hyperformula_security_certificate.pdf b/docs/.vuepress/public/hyperformula_security_certificate.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0c070db2356890cc807828dc92836e11d50a273a GIT binary patch literal 409753 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

`+KTT&6$^bYvp**r=+i} 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 z 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%0DTO&PE7BjTKoUYW5unBP?K{V4J8uSTvuj;(Km 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

owtHzruSW5eN(I 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 the security certificate (PDF) → + ## Quality Assurance Process Our development includes multiple quality layers: From 92e6c73ca7bac1aa49cb61fe23e4e113707d422b Mon Sep 17 00:00:00 2001 From: GreenFlux <24459976+GreenFlux@users.noreply.github.com> Date: Tue, 14 Jul 2026 04:46:55 -0400 Subject: [PATCH 16/28] Update contact link for feature requests (#1706) ### Context ### How did you test your changes? ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [ ] Change to the documentation ### Related issues: 1. Fixes #... 2. 3. ### Checklist: - [ ] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [ ] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [ ] My change is compatible with Microsoft Excel. - [ ] My change is compatible with Google Sheets. - [ ] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. - [ ] My changes require a documentation update. - [ ] My changes require a migration guide. --- > [!NOTE] > **Low Risk** > Config-only URL change with no runtime or security impact. > > **Overview** > Updates the **Feature requests and questions** contact link in the GitHub issue template config so it points to the [Handsontable forum HyperFormula category](https://forum.handsontable.com/t/about-the-hyperformula-category/9073) instead of GitHub Discussions. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 9638a7238c56a987e515fd76ed8479b198b8265d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Kuba Sekowski Co-authored-by: Claude Opus 4.6 Co-authored-by: Kuba Sekowski --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3d9865bb3..e9de124df 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,4 +1,4 @@ contact_links: - name: Feature requests and questions - url: https://github.com/handsontable/hyperformula/discussions + url: https://forum.handsontable.com/t/about-the-hyperformula-category/9073 about: Start a new discussion about your idea. From 3064a8b05c73aa9ad3c3127f774bcecf582f4bc0 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Tue, 14 Jul 2026 12:21:28 +0200 Subject: [PATCH 17/28] feat(functions): implement VSTACK and HSTACK (HF-71) (#1698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What Implements two new array-spilling functions, `VSTACK` and `HSTACK`, in `ArrayPlugin`. - **VSTACK(array1, [array2], ...)** — stacks arrays vertically. Result height = sum of input heights, width = max of input widths. Narrower inputs are padded on the right with `#N/A`. - **HSTACK(array1, [array2], ...)** — stacks arrays horizontally. Result width = sum of input widths, height = max of input heights. Shorter inputs are padded at the bottom with `#N/A`. Behaviour matches Excel 365 / Google Sheets, with the documented HyperFormula nuances below. ## Scope - `src/interpreter/plugin/ArrayPlugin.ts` — `VSTACK`/`HSTACK` metadata, methods, and `*ArraySize` parse-time size calculations (shared `stackSubChecks` helper; `VSTACK` aligns each row via `padRowToWidth`, `HSTACK` pads inline). - `src/i18n/languages/*.ts` — function-name entries for all 18 language packs (`enUS` inherits from `enGB`). The names are identical across locales, matching Excel's convention for these functions. - `docs/guide/built-in-functions.md` — entries for both functions. - `CHANGELOG.md` — `Added` entry under `[Unreleased]`. ## HyperFormula nuances vs. Excel - **Empty cells** pass through as empty (`null`) rather than coercing to `0`. HyperFormula preserves the empty value; Excel (which has no empty-result cell) displays `0`. The stacked structure is identical. - **A bare scalar argument that is itself an error** short-circuits the whole call to that error, the same way every `runFunction`-based function behaves (e.g. `ABS`, `FILTER`). Errors located *inside an input range* pass through per cell, preserving their type (matches Excel). ## Test coverage Unit tests live in the private tests repository: handsontable/hyperformula-tests#18 (57 cases: 28 VSTACK + 29 HSTACK), mirroring a validated Excel 365 oracle. They cover same-width/height stacks, dimension mismatch with `#N/A` padding, scalars, single-arg passthrough, mixed types, error passthrough, empty cells, jagged input from a custom function, empty-array error propagation, nested `VSTACK`/`HSTACK`, and integration with `SEQUENCE`/`TRANSPOSE`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- > [!NOTE] > **Low Risk** > Additive array functions in an isolated plugin with no changes to core engine, auth, or persistence; behavior is covered by external oracle tests. > > **Overview** > Adds **VSTACK** and **HSTACK** as new array-spilling functions in `ArrayPlugin`, aligned with Excel 365 / Google Sheets. > > **VSTACK** concatenates ranges vertically (height = sum of input heights, width = max width); narrower rows get `#N/A` padding on the right via `padRowToWidth`. **HSTACK** concatenates horizontally (width = sum of widths, height = max height); shorter inputs get `#N/A` at the bottom. Both use variadic `RANGE` arguments (`repeatLastArgs: 1`), array arithmetic on arguments, and paired `*ArraySize` methods for spill sizing; shared `stackSubChecks` resolves per-argument dimensions at parse time. > > Documentation and localization are updated: `built-in-functions.md`, `CHANGELOG.md` under Unreleased, and `VSTACK`/`HSTACK` entries in all 18 language packs (names kept as `VSTACK`/`HSTACK` like Excel). > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 3db462b6c39597e7064ca9a8fac7919e7a0eb710. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Claude Opus 4.8 Co-authored-by: Kuba Sekowski --- CHANGELOG.md | 1 + docs/guide/built-in-functions.md | 2 + src/i18n/languages/csCZ.ts | 2 + src/i18n/languages/daDK.ts | 2 + src/i18n/languages/deDE.ts | 2 + src/i18n/languages/enGB.ts | 2 + src/i18n/languages/esES.ts | 2 + src/i18n/languages/fiFI.ts | 2 + src/i18n/languages/frFR.ts | 2 + src/i18n/languages/huHU.ts | 2 + src/i18n/languages/idID.ts | 2 + src/i18n/languages/itIT.ts | 2 + src/i18n/languages/nbNO.ts | 2 + src/i18n/languages/nlNL.ts | 2 + src/i18n/languages/plPL.ts | 2 + src/i18n/languages/ptPT.ts | 2 + src/i18n/languages/ruRU.ts | 2 + src/i18n/languages/svSE.ts | 2 + src/i18n/languages/trTR.ts | 2 + src/interpreter/plugin/ArrayPlugin.ts | 149 +++++++++++++++++++++++++- 20 files changed, 185 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 113bcb492..36100532d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- Added new functions: VSTACK, HSTACK. [#1698](https://github.com/handsontable/hyperformula/pull/1698) - Added a new function: `XIRR`. [#1701](https://github.com/handsontable/hyperformula/pull/1701) - Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674) - Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145) diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index d96f97e78..d486c8e04 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -65,6 +65,8 @@ Total number of functions: **{{ $page.functionsCount }}** | FILTER | Filters an array, based on multiple conditions (boolean arrays). | FILTER(SourceArray, BoolArray1, BoolArray2, ...BoolArrayN) | | ARRAY_CONSTRAIN | Truncates an array to given dimensions. | ARRAY_CONSTRAIN(Array, Height, Width) | | SEQUENCE | Returns an array of sequential numbers. | SEQUENCE(Rows, [Cols], [Start], [Step]) | +| VSTACK | Stacks arrays vertically into a single array. | VSTACK(Array1, [Array2], ...[ArrayN]) | +| HSTACK | Stacks arrays horizontally into a single array. | HSTACK(Array1, [Array2], ...[ArrayN]) | ### Date and time diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 5a0b8ef00..ff9649112 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ODKAZ', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 39a83b4fc..6a44e95ce 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADRESSE', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 4a3046ce8..7633eb14f 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADRESSE', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 7e9130cd4..7011a8162 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADDRESS', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index eb57d2d4f..4d3a8217f 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -19,6 +19,8 @@ export const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'DIRECCION', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 655643e6a..55f13bd03 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'OSOITE', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index a30d4a118..de69b12ec 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADRESSE', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 03b6b9dcf..200787a5e 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'CÍM', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts index 0a3f025bc..72e69bb05 100644 --- a/src/i18n/languages/idID.ts +++ b/src/i18n/languages/idID.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ALAMAT', 'ARRAY_CONSTRAIN': 'BATASAN.MATRIKS', ARRAYFORMULA: 'RUMUS.MATRIKS', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 7dee12d8d..93a6f9a69 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'INDIRIZZO', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index dcc51c4be..a0c9b9e47 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADRESSE', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 118f7b497..f8c1eed96 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADRES', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 262c2459e..b6b8aa5ec 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADRES', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 5d048e179..3d9386d9f 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ENDEREÇO', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index c42ef694c..c247b3050 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'АДРЕС', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 90f9357f2..13fae5523 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADRESS', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 6e898ed36..8196edc45 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -19,6 +19,8 @@ const dictionary: RawTranslationPackage = { }, functions: { FILTER: 'FILTER', + VSTACK: 'VSTACK', + HSTACK: 'HSTACK', ADDRESS: 'ADRES', 'ARRAY_CONSTRAIN': 'ARRAY_CONSTRAIN', ARRAYFORMULA: 'ARRAYFORMULA', diff --git a/src/interpreter/plugin/ArrayPlugin.ts b/src/interpreter/plugin/ArrayPlugin.ts index d9915f7f4..27b47096e 100644 --- a/src/interpreter/plugin/ArrayPlugin.ts +++ b/src/interpreter/plugin/ArrayPlugin.ts @@ -42,7 +42,25 @@ export class ArrayPlugin extends FunctionPlugin implements FunctionPluginTypeche {argumentType: FunctionArgumentType.RANGE}, ], repeatLastArgs: 1, - } + }, + 'VSTACK': { + method: 'vstack', + sizeOfResultArrayMethod: 'vstackArraySize', + enableArrayArithmeticForArguments: true, + parameters: [ + {argumentType: FunctionArgumentType.RANGE}, + ], + repeatLastArgs: 1, + }, + 'HSTACK': { + method: 'hstack', + sizeOfResultArrayMethod: 'hstackArraySize', + enableArrayArithmeticForArguments: true, + parameters: [ + {argumentType: FunctionArgumentType.RANGE}, + ], + repeatLastArgs: 1, + }, } public arrayformula(ast: ProcedureAst, state: InterpreterState): InterpreterValue { @@ -147,4 +165,133 @@ export class ArrayPlugin extends FunctionPlugin implements FunctionPluginTypeche const height = Math.max(...(subChecks).map(val => val.height)) return new ArraySize(width, height) } + + /** + * Corresponds to VSTACK(array1, [array2], ...) + * + * Stacks the input arrays vertically, one on top of another, into a single array. + * The result has as many rows as the inputs combined and as many columns as the + * widest input. Cells of narrower inputs are padded on the right with the #N/A + * error, matching the behaviour of Excel and Google Sheets. + * + * @param ast + * @param state + */ + public vstack(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('VSTACK'), (...ranges: SimpleRangeValue[]) => { + const width = Math.max(...ranges.map(range => range.width())) + const result: InternalScalarValue[][] = [] + + for (const range of ranges) { + for (const row of range.data) { + result.push(this.padRowToWidth(row, width)) + } + } + + return SimpleRangeValue.onlyValues(result) + }) + } + + /** + * Calculates the spilled array size of VSTACK: the width is the widest input + * and the height is the sum of all input heights. + * + * @param ast + * @param state + */ + public vstackArraySize(ast: ProcedureAst, state: InterpreterState): ArraySize { + if (ast.args.length < 1) { + return ArraySize.error() + } + + const subChecks = this.stackSubChecks(ast, state, 'VSTACK') + const width = Math.max(...subChecks.map(size => size.width)) + const height = subChecks.reduce((total, size) => total + size.height, 0) + return new ArraySize(width, height) + } + + /** + * Corresponds to HSTACK(array1, [array2], ...) + * + * Stacks the input arrays horizontally, side by side, into a single array. + * The result has as many columns as the inputs combined and as many rows as the + * tallest input. Cells of shorter inputs are padded at the bottom with the #N/A + * error, matching the behaviour of Excel and Google Sheets. + * + * @param ast + * @param state + */ + public hstack(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('HSTACK'), (...ranges: SimpleRangeValue[]) => { + const height = Math.max(...ranges.map(range => range.height())) + const result: InternalScalarValue[][] = [...Array(height).keys()].map(() => []) + + for (const range of ranges) { + const data = range.data + const width = range.width() + for (let row = 0; row < height; row++) { + const sourceRow = row < data.length ? data[row] : undefined + for (let col = 0; col < width; col++) { + // Pad both missing rows (sourceRow === undefined) and short rows + // (col beyond the row's length) with #N/A, exactly as VSTACK does. + result[row].push(sourceRow !== undefined && col < sourceRow.length + ? sourceRow[col] + : new CellError(ErrorType.NA, ErrorMessage.ValueNotFound)) + } + } + } + + return SimpleRangeValue.onlyValues(result) + }) + } + + /** + * Calculates the spilled array size of HSTACK: the width is the sum of all + * input widths and the height is the tallest input. + * + * @param ast + * @param state + */ + public hstackArraySize(ast: ProcedureAst, state: InterpreterState): ArraySize { + if (ast.args.length < 1) { + return ArraySize.error() + } + + const subChecks = this.stackSubChecks(ast, state, 'HSTACK') + const width = subChecks.reduce((total, size) => total + size.width, 0) + const height = Math.max(...subChecks.map(size => size.height)) + return new ArraySize(width, height) + } + + /** + * Resolves the array size of every argument of a stacking function, enabling + * array arithmetic for the arguments when the function's metadata requests it. + * + * @param ast + * @param state + * @param functionName - the stacking function whose metadata drives the array-arithmetic flag + */ + private stackSubChecks(ast: ProcedureAst, state: InterpreterState, functionName: 'VSTACK' | 'HSTACK'): ArraySize[] { + const metadata = this.metadata(functionName) + return ast.args.map((arg) => this.arraySizeForAst(arg, new InterpreterState(state.formulaAddress, state.arraysFlag || (metadata?.enableArrayArithmeticForArguments ?? false)))) + } + + /** + * Returns a copy of the given row resized to exactly `width` cells: longer + * rows are truncated and shorter rows are padded on the right with #N/A. Used + * by VSTACK to align every stacked row to the widest input. + * + * @param row - the source row to resize + * @param width - the target number of cells + */ + private padRowToWidth(row: InternalScalarValue[], width: number): InternalScalarValue[] { + if (row.length >= width) { + return row.slice(0, width) + } + const padded = row.slice() + while (padded.length < width) { + padded.push(new CellError(ErrorType.NA, ErrorMessage.ValueNotFound)) + } + return padded + } } From e0e1261cbd59fe06115205c784dac394dd0308da Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Wed, 15 Jul 2026 08:45:18 +0200 Subject: [PATCH 18/28] docs: clarify setRowOrder/setColumnOrder permutation semantics (#1687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Context Fixes [#1668](https://github.com/handsontable/hyperformula/issues/1668). The reporter ran `setRowOrder` expecting the array argument to mean "previous position for each new row", got the opposite behavior, and was unable to disambiguate from the docs. Root cause: every example in the JSDoc and guide pages used a *self-inverse* permutation (`[2, 1, 0]` on 3 rows, `[0, 3, 2, 1]` on 4 rows). Self-inverse permutations produce the same output under both of the two natural interpretations of the argument: - A. `newOrder[i]` = new position for the row/column currently at index `i` (this is the actual behavior, verified in `mappingFromOrder` in `src/CrudOperations.ts` and `Operations.setRowOrder` in `src/Operations.ts`) - B. `newOrder[i]` = previous position of the row/column that should end up at index `i` So the examples didn't actually demonstrate which interpretation was correct. ### Changes Doc-only. No runtime behavior changed. - Replaced the self-inverse examples with non-self-inverse cyclic shifts (`[1, 2, 0]` on a 3-element sheet → result `[['C'], ['A'], ['B']]`). Under interpretation B the result would have been `[['B'], ['C'], ['A']]`, so the example now visibly reinforces the correct semantics. - Added explicit prose to the JSDoc and guide pages stating the convention: "the value at index `i` is the new position for the row/column currently at index `i`". - Added a `::: warning` callout in the sorting guide explicitly contrasting the correct interpretation with the inverse-permutation interpretation that the reporter assumed. - Updated the analogous `setColumnOrder`/`isItPossibleToSetColumnOrder` documentation for consistency. - Added a note to `DEV_DOCS.md` clarifying that documentation-only PRs do not require a `CHANGELOG.md` entry. Files touched: - `src/HyperFormula.ts` - JSDoc for `setRowOrder`, `isItPossibleToSetRowOrder`, `setColumnOrder`, `isItPossibleToSetColumnOrder`. - `docs/guide/sorting-data.md` - intro paragraph, "Sorting rows" and "Sorting columns" step-by-step sections. - `docs/guide/basic-operations.md` - "Reordering rows" and "Reordering columns" subsections. - `DEV_DOCS.md` - new "Documentation-only changes" note under "Definition of Done". Out of scope: `swapRowIndexes` / `swapColumnIndexes` use `[[source, target], ...]` pairs and are already described as "array mapping original positions to final positions"; the ambiguity does not apply, so those are untouched. ### How did you test your changes? - `npm run lint` - 0 errors (pre-existing warnings in tests are unrelated). - `npm run compile` - clean TypeScript compile. - Read the rendered JSDoc and Markdown by inspection to confirm the new examples and prose are accurate against the verified implementation. ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: 1. Fixes #1668 ### Checklist: - [x] I have reviewed the guidelines about [Contributing to HyperFormula](https://hyperformula.handsontable.com/guide/contributing.html) and I confirm that my code follows the code style of this project. - [ ] I have signed the [Contributor License Agreement](https://goo.gl/forms/yuutGuN0RjsikVpM2). - [x] My change is compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard. - [x] My change is compatible with Microsoft Excel. - [x] My change is compatible with Google Sheets. - [ ] I described my changes in the [CHANGELOG.md](https://github.com/handsontable/hyperformula/blob/master/CHANGELOG.md) file. (Not required: documentation-only change.) - [x] My changes require a documentation update. - [ ] My changes require a migration guide.
Open in Web Open in Cursor 
--------- Co-authored-by: GreenFlux Co-authored-by: Claude Opus 4.6 Co-authored-by: Cursor Agent Co-authored-by: Kuba Sekowski --- DEV_DOCS.md | 2 +- docs/guide/basic-operations.md | 12 ++-- docs/guide/sorting-data.md | 103 +++++++++++++++++---------------- src/HyperFormula.ts | 52 +++++++++++------ 4 files changed, 97 insertions(+), 72 deletions(-) diff --git a/DEV_DOCS.md b/DEV_DOCS.md index 9ca91f9aa..4232cb2ff 100644 --- a/DEV_DOCS.md +++ b/DEV_DOCS.md @@ -73,7 +73,7 @@ Each change to the production code (bugfix, new feature, or improvement) must in - Updates to documentation related to the change - for breaking changes: a section in the migration guide - Technical documentation in the form of JSDoc comments (high-level description of the concepts used in more complex code fragments) -- Changelog entry +- Changelog entry (not required for documentation-only changes (guides, JSDoc, README, etc.) - Pull request description A single pull request should contain an atomic self-contained functional change (single bugfix, single feature, single improvement). If a pull request contains multiple features or bugfixes, it should be split. diff --git a/docs/guide/basic-operations.md b/docs/guide/basic-operations.md index 20a4b1edc..f6c25fd7c 100644 --- a/docs/guide/basic-operations.md +++ b/docs/guide/basic-operations.md @@ -151,11 +151,13 @@ You can change the order of rows by using the [`setRowOrder`](../api/classes/hyp * Sheet ID * [New row order](../api/classes/hyperformula.md#setroworder) +The new row order is a permutation of the form `[ newPositionForRow0, newPositionForRow1, newPositionForRow2, ... ]`. The value at index `i` is the new position for the row that is currently at index `i`. See the [Sorting data](sorting-data.md) guide for details. + This method returns [an array of changed cells](#changes-array). ```javascript -// row 0 and row 2 swap places -const changes = hfInstance.setRowOrder(0, [2, 1, 0]); +// move row 0 to position 1, row 1 to position 2, and row 2 to position 0 +const changes = hfInstance.setRowOrder(0, [1, 2, 0]); ``` ## Columns @@ -212,11 +214,13 @@ You can change the order of columns by using the [`setColumnOrder`](../api/class * Sheet ID * [New column order](../api/classes/hyperformula.md#setcolumnorder) +The new column order is a permutation of the form `[ newPositionForColumn0, newPositionForColumn1, newPositionForColumn2, ... ]`. The value at index `i` is the new position for the column that is currently at index `i`. See the [Sorting data](sorting-data.md) guide for details. + This method returns [an array of changed cells](#changes-array). ```javascript -// column 0 and column 2 swap places -const changes = hfInstance.setColumnOrder(0, [2, 1, 0]); +// move column 0 to position 1, column 1 to position 2, and column 2 to position 0 +const changes = hfInstance.setColumnOrder(0, [1, 2, 0]); ``` ## Cells diff --git a/docs/guide/sorting-data.md b/docs/guide/sorting-data.md index 8faedd3ca..e9920cd41 100644 --- a/docs/guide/sorting-data.md +++ b/docs/guide/sorting-data.md @@ -6,32 +6,41 @@ In HyperFormula, you can sort data by reordering rows and columns. To sort data in HyperFormula, you reorder rows (or columns), by providing your preferred permutation of row (or column) indexes. -You can implement any sorting algorithm that returns an array of row or column indexes. +The permutation array has the form `[ newPositionForRow0, newPositionForRow1, newPositionForRow2, ... ]`. The value at index `i` is the new position for the row that is currently at index `i`. + +You can implement any sorting algorithm that returns such an array of row or column indexes. ## Sorting rows To sort rows, use the [`isItPossibleToSetRowOrder`](../api/classes/hyperformula.md#isitpossibletosetroworder) and [`setRowOrder`](../api/classes/hyperformula.md#setroworder) methods. ### Step 1: Choose a new row order -Choose your required permutation of row indexes. +Choose your required permutation of row indexes. -For example, if you want to swap the first row with the third row, set the order to `[2, 1, 0]` instead of `[0, 1, 2]`: +For example, if you want to move the bottom row to the top of a 3-row sheet, set the order to `[1, 2, 0]` instead of `[0, 1, 2]`. This moves the row at index 0 to position 1, the row at index 1 to position 2, and the row at index 2 to position 0: ```js // a HyperFormula instance with example data const hfInstance = HyperFormula.buildFromArray([ - [1], - [2], - [4, 5], + ['A'], + ['B'], + ['C'], ]); -// we'll set the row order to [2, 1, 0] in the next steps +// we'll set the row order to [1, 2, 0] in the next steps +// the resulting sheet will be: [['C'], ['A'], ['B']] ``` ::: tip The [`setRowOrder`](../api/classes/hyperformula.md#setroworder) method accepts an array of numbers, so you can implement any function that returns an array with your required row order. ::: +::: warning +The permutation array maps **current positions** to **new positions**, not the other way around. The value at index `i` tells HyperFormula where to move the row currently at index `i`, *not* which row should end up at index `i`. + +For example, `[1, 2, 0]` means "move row 0 to position 1, row 1 to position 2, row 2 to position 0". It does **not** mean "the new row 0 comes from position 1, the new row 1 comes from position 2, ...". +::: + ### Step 2: Check if the new row order can be applied Before you change the row order, check if your specified row number permutation can actually be applied. @@ -42,16 +51,16 @@ Use the [`isItPossibleToSetRowOrder`](../api/classes/hyperformula.md#isitpossibl ```js const hfInstance = HyperFormula.buildFromArray([ - [1], - [2], - [4, 5], + ['A'], + ['B'], + ['C'], ]); // a variable to carry the user message let messageUsedInUI; // check if your permutation can be applied -const isRowOrderOk = hfInstance.isItPossibleToSetRowOrder(0, [2, 1, 0]); +const isRowOrderOk = hfInstance.isItPossibleToSetRowOrder(0, [1, 2, 0]); // display an error message if (!isRowOrderOk) { @@ -65,39 +74,35 @@ If your specified row number permutation is valid, change the row order: ```js const hfInstance = HyperFormula.buildFromArray([ - [1], - [2], - [4, 5], + ['A'], + ['B'], + ['C'], ]); let messageUsedInUI; -const isRowOrderOk = hfInstance.isItPossibleToSetRowOrder(0, [2, 1, 0]); +const isRowOrderOk = hfInstance.isItPossibleToSetRowOrder(0, [1, 2, 0]); if (!isRowOrderOk) { messageUsedInUI = 'Sorry, you cannot sort rows in this way.' } else { // set the new row order - setRowOrder(0, [2, 1, 0]); + hfInstance.setRowOrder(0, [1, 2, 0]); } -// rows 0 and 2 swap places +// the resulting sheet is: [['C'], ['A'], ['B']] -// returns: +// the method returns an array of cells whose values changed: // [{ -// address: { sheet: 0, col: 0, row: 2 }, -// newValue: 1, +// address: { sheet: 0, col: 0, row: 1 }, +// newValue: 'A', // }, // { -// address: { sheet: 0, col: 1, row: 2 }, -// newValue: null, +// address: { sheet: 0, col: 0, row: 2 }, +// newValue: 'B', // }, // { // address: { sheet: 0, col: 0, row: 0 }, -// newValue: 4, -// }, -// { -// address: { sheet: 0, col: 1, row: 0 }, -// newValue: 5, +// newValue: 'C', // }] ``` @@ -105,25 +110,31 @@ if (!isRowOrderOk) { To sort columns, use the [`isItPossibleToSetColumnOrder`](../api/classes/hyperformula.md#isitpossibletosetcolumnorder) and [`setColumnOrder`](../api/classes/hyperformula.md#setcolumnorder) methods. +The permutation array has the same shape as for rows: `[ newPositionForColumn0, newPositionForColumn1, newPositionForColumn2, ... ]`. The value at index `i` is the new position for the column that is currently at index `i`. + ### Step 1: Choose a new column order Choose your required permutation of column indexes. -For example, if you want to swap the first column with the third column, set the order to `[2, 1, 0]` instead of `[0, 1, 2]`: +For example, if you want to move the last column to the front of a 3-column sheet, set the order to `[1, 2, 0]` instead of `[0, 1, 2]`. This moves the column at index 0 to position 1, the column at index 1 to position 2, and the column at index 2 to position 0: ```js // a HyperFormula instance with example data const hfInstance = HyperFormula.buildFromArray([ - [1, 2, 4], - [5] + ['A', 'B', 'C'] ]); -// we'll set the column order to [2, 1, 0] in the next steps +// we'll set the column order to [1, 2, 0] in the next steps +// the resulting sheet will be: [['C', 'A', 'B']] ``` ::: tip The [`setColumnOrder`](../api/classes/hyperformula.md#setcolumnorder) method accepts an array of numbers, so you can implement any function that returns an array with your required column order. ::: +::: warning +The permutation array maps **current positions** to **new positions**, not the other way around. The value at index `i` tells HyperFormula where to move the column currently at index `i`, *not* which column should end up at index `i`. +::: + ### Step 2: Check if the new column order can be applied Before you change the column order, check if your specified column number permutation can actually be applied. @@ -134,15 +145,14 @@ Use the [`isItPossibleToSetColumnOrder`](../api/classes/hyperformula.md#isitposs ```js const hfInstance = HyperFormula.buildFromArray([ - [1, 2, 4], - [5] + ['A', 'B', 'C'] ]); // a variable to carry the user message let messageUsedInUI; // check if your permutation can be applied -const isColumnOrderOk = hfInstance.isItPossibleToSetColumnOrder(0, [2, 1, 0]); +const isColumnOrderOk = hfInstance.isItPossibleToSetColumnOrder(0, [1, 2, 0]); // display an error message if (!isColumnOrderOk) { @@ -156,38 +166,33 @@ If your specified column number permutation is valid, change the column order: ```js const hfInstance = HyperFormula.buildFromArray([ - [1, 2, 4], - [5] + ['A', 'B', 'C'] ]); let messageUsedInUI; -const isColumnOrderOk = hfInstance.isItPossibleToSetColumnOrder(0, [2, 1, 0]); +const isColumnOrderOk = hfInstance.isItPossibleToSetColumnOrder(0, [1, 2, 0]); if (!isColumnOrderOk) { messageUsedInUI = 'Sorry, you cannot sort columns in this way.' } else { // set the new column order - setColumnOrder(0, [2, 1, 0]); + hfInstance.setColumnOrder(0, [1, 2, 0]); } -// columns 0 and 2 swap places +// the resulting sheet is: [['C', 'A', 'B']] -//returns: +// the method returns an array of cells whose values changed: // [{ -// address: { sheet: 0, col: 2, row: 0 }, -// newValue: 1, +// address: { sheet: 0, col: 1, row: 0 }, +// newValue: 'A', // }, // { -// address: { sheet: 0, col: 2, row: 1 }, -// newValue: 5, +// address: { sheet: 0, col: 2, row: 0 }, +// newValue: 'B', // }, // { // address: { sheet: 0, col: 0, row: 0 }, -// newValue: 4, -// }, -// { -// address: { sheet: 0, col: 0, row: 1 }, -// newValue: null, +// newValue: 'C', // }] ``` diff --git a/src/HyperFormula.ts b/src/HyperFormula.ts index ec342be6b..7e56d4c61 100644 --- a/src/HyperFormula.ts +++ b/src/HyperFormula.ts @@ -1389,7 +1389,11 @@ export class HyperFormula implements TypedEmitter { /** * Reorders rows of a sheet according to a permutation of 0-based indexes. - * Parameter `newRowOrder` should have a form `[ newPositionForRow0, newPositionForRow1, newPositionForRow2, ... ]`. + * + * Parameter `newRowOrder` should have the form `[ newPositionForRow0, newPositionForRow1, newPositionForRow2, ... ]`. + * In other words, the value at index `i` is the new position for the row that is currently at index `i`. + * Note that this is the opposite of `[ previousPositionForRow0, previousPositionForRow1, ... ]`. + * * This method might be used to [sort the rows of a sheet](../../guide/sorting-data.md). * * Returns [an array of cells whose values changed as a result of this operation](/guide/basic-operations.md#changes-array). @@ -1411,15 +1415,15 @@ export class HyperFormula implements TypedEmitter { * const hfInstance = HyperFormula.buildFromArray([ * ['A'], * ['B'], - * ['C'], - * ['D'] + * ['C'] * ]); * - * const newRowOrder = [0, 3, 2, 1]; // [ newPosForA, newPosForB, newPosForC, newPosForD ] + * // Move 'A' to index 1, 'B' to index 2, and 'C' to index 0. + * const newRowOrder = [1, 2, 0]; // [ newPosForA, newPosForB, newPosForC ] * * const changes = hfInstance.setRowOrder(0, newRowOrder); * - * // Sheet after this operation: [['A'], ['D'], ['C'], ['B']] + * // Sheet after this operation: [['C'], ['A'], ['B']] * ``` * * @category Rows @@ -1433,6 +1437,10 @@ export class HyperFormula implements TypedEmitter { /** * Checks if it is possible to reorder rows of a sheet according to a permutation. * + * Parameter `newRowOrder` should have the form `[ newPositionForRow0, newPositionForRow1, newPositionForRow2, ... ]`, + * i.e. the value at index `i` is the new position for the row that is currently at index `i`. + * See [[setRowOrder]] for details. + * * @param {number} sheetId - ID of a sheet to operate on * @param {number[]} newRowOrder - permutation of rows * @@ -1441,15 +1449,15 @@ export class HyperFormula implements TypedEmitter { * @example * ```js * const hfInstance = HyperFormula.buildFromArray([ - * [1], - * [2], - * [4, 5], + * ['A'], + * ['B'], + * ['C'] * ]); * * // returns true - * hfInstance.isItPossibleToSetRowOrder(0, [2, 1, 0]); + * hfInstance.isItPossibleToSetRowOrder(0, [1, 2, 0]); * - * // returns false + * // returns false (array length must match the number of rows) * hfInstance.isItPossibleToSetRowOrder(0, [2]); * ``` * @@ -1554,7 +1562,11 @@ export class HyperFormula implements TypedEmitter { /** * Reorders columns of a sheet according to a permutation of 0-based indexes. - * Parameter `newColumnOrder` should have a form `[ newPositionForColumn0, newPositionForColumn1, newPositionForColumn2, ... ]`. + * + * Parameter `newColumnOrder` should have the form `[ newPositionForColumn0, newPositionForColumn1, newPositionForColumn2, ... ]`. + * In other words, the value at index `i` is the new position for the column that is currently at index `i`. + * Note that this is the opposite of `[ previousPositionForColumn0, previousPositionForColumn1, ... ]`. + * * This method might be used to [sort the columns of a sheet](../../guide/sorting-data.md). * * Returns [an array of cells whose values changed as a result of this operation](/guide/basic-operations.md#changes-array). @@ -1574,14 +1586,15 @@ export class HyperFormula implements TypedEmitter { * @example * ```js * const hfInstance = HyperFormula.buildFromArray([ - * ['A', 'B', 'C', 'D'] + * ['A', 'B', 'C'] * ]); * - * const newColumnOrder = [0, 3, 2, 1]; // [ newPosForA, newPosForB, newPosForC, newPosForD ] + * // Move 'A' to index 1, 'B' to index 2, and 'C' to index 0. + * const newColumnOrder = [1, 2, 0]; // [ newPosForA, newPosForB, newPosForC ] * * const changes = hfInstance.setColumnOrder(0, newColumnOrder); * - * // Sheet after this operation: [['A', 'D', 'C', 'B']] + * // Sheet after this operation: [['C', 'A', 'B']] * ``` * * @category Columns @@ -1595,6 +1608,10 @@ export class HyperFormula implements TypedEmitter { /** * Checks if it is possible to reorder columns of a sheet according to a permutation. * + * Parameter `newColumnOrder` should have the form `[ newPositionForColumn0, newPositionForColumn1, newPositionForColumn2, ... ]`, + * i.e. the value at index `i` is the new position for the column that is currently at index `i`. + * See [[setColumnOrder]] for details. + * * @param {number} sheetId - ID of a sheet to operate on * @param {number[]} newColumnOrder - permutation of columns * @@ -1603,14 +1620,13 @@ export class HyperFormula implements TypedEmitter { * @example * ```js * const hfInstance = HyperFormula.buildFromArray([ - * [1, 2, 4], - * [5] + * ['A', 'B', 'C'] * ]); * * // returns true - * hfInstance.isItPossibleToSetColumnOrder(0, [2, 1, 0]); + * hfInstance.isItPossibleToSetColumnOrder(0, [1, 2, 0]); * - * // returns false + * // returns false (array length must match the number of columns) * hfInstance.isItPossibleToSetColumnOrder(0, [1]); * ``` * From fd04f77da236bc1f5fa8bd7412aad8ca447b3800 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Thu, 16 Jul 2026 13:58:30 +0200 Subject: [PATCH 19/28] fix(lookup): skip empty cells in approximate MATCH/VLOOKUP/HLOOKUP/XLOOKUP (HF-223) (#1697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What & why Approximate `MATCH`, `VLOOKUP`, `HLOOKUP` and `XLOOKUP` returned `#N/A` (or, in descending mode, the wrong position) when the sorted search range contained genuinely empty cells. Excel 2021 and Google Sheets skip empty cells when computing the lower/upper bound; HyperFormula instead landed on an empty cell during binary search (its `EmptyValue` Symbol never matched the key). [HF-223] ## Root cause `findLastOccurrenceInOrderedRange` (`src/interpreter/binarySearch.ts`): `compare()` ranks `EmptyValue` below every value, breaking the sort invariant the binary search relies on, and the `typeof foundValue !== typeof searchKey` guard then turned a landing-on-empty into `#N/A`. Shared by approximate `MATCH(±1)`, sorted `VLOOKUP`/`HLOOKUP`, and `XLOOKUP(searchMode ±2)`. ## Fix - The binary search runs directly over the original range while tracking whether the descent ever probes an empty cell. Empty cells are the only source of non-monotonicity in the search predicate, so a descent that never touches one is provably equivalent to a search over the range with empty cells removed and its result is trusted as-is — the common case stays `O(log n)`, including ranges that contain empty cells the descent happens not to touch. - Only when the descent probes an empty cell does the search fall back to an `O(n)` compaction: the non-empty cell indices are collected, the binary search re-runs over the compacted list, and the result maps back to the original index space, so empty cells keep their slots and the matched non-empty cell's original 1-based position is reported unchanged. (In exact-match mode, a hit found by the direct descent is accepted after an equality re-check, skipping the fallback.) - The "no match at all" bound cases return the position of the first **non-empty** cell (never the position of a leading empty cell), and the approximate-bound "next" position steps to the next *non-empty* index, so skipped empty slots never shift the reported position. - `AdvancedFind.findNormalizedValue` skips `EmptyValue` on its in-memory ordered path for the same reason, keeping the linear and binary search modes consistent. - A matched result cell that is empty is returned as `0` (`zeroIfEmptyResult`), matching Excel, for `VLOOKUP`/`HLOOKUP`/`XLOOKUP` including multi-cell `XLOOKUP` return arrays. - Empty strings are unaffected (text ranks above numbers, so they still terminate a numeric run). ## Performance & edge cases - **Complexity:** `O(log n)` whenever the binary-search descent probes no empty cell (always the case for gap-free ranges — the typical sorted-lookup workload); `O(n)` only when an empty cell actually interferes with the descent. A new `Sorted lookup` benchmark in the performance suite guards this fast path. - **All-empty range:** returns `NOT_FOUND` directly (the `if_not_found` result / `#N/A`), never row 1. - **Leading empty cells with the key outside the range:** the bound modes return the first non-empty position, consistent with the linear search modes on the same data. - **Duplicates caveat:** in exact-match binary modes, when the range contains both duplicates of the key and interspersed empty cells, which duplicate is reported is unspecified (Excel's binary modes likewise leave this unspecified); documented in the function's JSDoc. ## Excel / Google Sheets parity Behaviour verified against the latest Excel and Google Sheets, including exact-vs-blank, empty-string-not-skipped, and the descending early-stop case. For the leading-empty bound cases, HyperFormula's binary modes are consistent with its own linear modes (Excel documents binary search over blank-containing ranges as unreliable). ## Tests Public test suite: handsontable/hyperformula-tests#17 (matching branch `task/hf-223-match-empty-cells`), including regression locks for the stepping and leading-empty edge cases and a `Sorted lookup` performance benchmark. ## Definition of Done - [x] Production code + JSDoc - [x] Tests (hyperformula-tests#17, matching branch) - [x] Changelog (empty-cell search fix + empty-result → 0 coercion) - [x] i18n — N/A (shared-logic fix, no function add/rename) - [x] Docs — list-of-differences entry for the remaining Excel divergence --- > [!NOTE] > **Medium Risk** > Changes shared lookup/binary-search logic used by MATCH and all major lookup functions, so incorrect edge-case handling could affect many formulas; scope is limited to lookup parity and is behavior-fix oriented rather than new surface area. > > **Overview** > Fixes **HF-223**: approximate lookups no longer break when the search range has **genuinely empty cells** interspersed among sorted values. > > **Search behavior:** `findLastOccurrenceInOrderedRange` now treats `EmptyValue` as non-participating in ordering. It keeps an **O(log n)** binary search when the descent never hits an empty cell; if it does, it **compacts non-empty indices** and re-searches, with exact-match hits still accepted after an equality re-check. Lower/upper bound paths step to the **first/next non-empty** index instead of landing on blanks or returning row 1 on all-empty ranges. The in-memory path in `AdvancedFind.findNormalizedValue` skips empty cells the same way so linear and binary modes stay aligned. > > **Return values:** `VLOOKUP`, `HLOOKUP`, and `XLOOKUP` coerce an **empty matched result cell to `0`**, matching Excel. > > Changelog and **list-of-differences** document the fix and remaining Excel divergence on binary `XLOOKUP` over blank-heavy ranges. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d3a6f2138468daebf6454ad5bb9cab8eeb8f162f. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Claude Opus 4.8 Co-authored-by: Kuba Sekowski Co-authored-by: Kuba Sekowski --- CHANGELOG.md | 5 ++ docs/guide/list-of-differences.md | 1 + src/Lookup/AdvancedFind.ts | 15 ++++ src/interpreter/binarySearch.ts | 115 ++++++++++++++++++++++--- src/interpreter/plugin/LookupPlugin.ts | 20 ++++- 5 files changed, 141 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36100532d..94424f8a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674) - Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145) +### Fixed + +- Fixed the behavior of `MATCH`, `VLOOKUP`, `HLOOKUP`, and `XLOOKUP` functions when the search range contained empty cells. [#1697](https://github.com/handsontable/hyperformula/pull/1697) +- Fixed the `VLOOKUP`, `HLOOKUP`, and `XLOOKUP` functions to return `0` instead of an empty value when the matched cell in the result range is empty. [#1697](https://github.com/handsontable/hyperformula/pull/1697) + ## [3.3.0] - 2026-05-20 ### Added diff --git a/docs/guide/list-of-differences.md b/docs/guide/list-of-differences.md index aa0e684fe..9f02316a5 100644 --- a/docs/guide/list-of-differences.md +++ b/docs/guide/list-of-differences.md @@ -41,6 +41,7 @@ See a full list of differences between HyperFormula, Microsoft Excel, and Google | TIMEVALUE function | =TIMEVALUE("14:31") | Type of the returned value: `CellValueDetailedType.NUMBER_TIME` (compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard) | Cell auto-formatted as **regular number** | Cell auto-formatted as **regular number** | | EDATE function | =EDATE(DATE(2019, 7, 31), 1) | Type of the returned value: `CellValueDetailedType.NUMBER_DATE`. This is non-compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard, which defines the return type as a Number, while describing it as a Date serial number through the function summary. | Cell auto-formatted as **date** | Cell auto-formatted as **regular number** | | EOMONTH function | =EOMONTH(DATE(2019, 7, 31), 1) | Type of the returned value: `CellValueDetailedType.NUMBER_DATE`. This is non-compliant with the [OpenDocument](https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part4-formula/OpenDocument-v1.3-os-part4-formula.html) standard, which defines the return type as a Number, while describing it as a Date serial number through the function summary. | Cell auto-formatted as **date** | Cell auto-formatted as **regular number** | +| Empty cells in lookup search | =XLOOKUP(3, A1:A4, A1:A4, "NF", 0, 2)
where A1:A4 = 1, 2, (empty), 3 | Empty cells are skipped during the search. A value that is present is found even when it sits past an interspersed empty cell (exact match is gap-independent), and approximate `MATCH`/`VLOOKUP`/`HLOOKUP`/`XLOOKUP` skip empty cells — but not empty strings — when finding the lower/upper bound. Returns `3`. On an all-empty range in a binary search mode, HyperFormula returns the `if_not_found` result (never row 1). | Skips empty cells in approximate search (parity with HyperFormula). | With binary search modes (`search_mode` ±2), a range with interspersed empty cells is not strictly sorted; per Excel's documentation the result may be invalid, so a value past an empty cell is not reliably found. On an all-empty range in a binary mode, Excel returns the first row's value. | ## Built-in functions diff --git a/src/Lookup/AdvancedFind.ts b/src/Lookup/AdvancedFind.ts index 0c5f0d73b..5c5943cde 100644 --- a/src/Lookup/AdvancedFind.ts +++ b/src/Lookup/AdvancedFind.ts @@ -5,6 +5,7 @@ import {DependencyGraph} from '../DependencyGraph' import { + EmptyValue, getRawValue, InternalScalarValue, RawInterpreterValue, @@ -61,6 +62,13 @@ export abstract class AdvancedFind { ) } + /** + * Linear search over an in-memory array for the value equal to `searchKey`, or — when `ifNoMatch` + * is `returnLowerBound`/`returnUpperBound` — the closest non-exceeding/non-preceding value. + * Genuinely empty cells (`EmptyValue`) are skipped, consistent with `findLastOccurrenceInOrderedRange` + * and with Excel/Google Sheets, which ignore empty cells (but not empty strings) in approximate search. + * Returns the 0-based index into `searchArray`, or `NOT_FOUND` (-1) when nothing matches. + */ protected findNormalizedValue(searchKey: RawNoErrorScalarValue, searchArray: InternalScalarValue[], ifNoMatch: 'returnLowerBound' | 'returnUpperBound' | 'returnNotFound' = 'returnNotFound', returnOccurrence: 'first' | 'last' = 'first'): number { const normalizedArray = searchArray .map(getRawValue) @@ -88,6 +96,13 @@ export abstract class AdvancedFind { return i } + // Skip empty cells in the approximate search, consistent with findLastOccurrenceInOrderedRange: + // Excel/Google Sheets ignore genuinely empty cells (but not empty strings) when looking for the + // lower/upper bound. EmptyValue would otherwise be ranked below every value by compare(). + if (value === EmptyValue) { + continue + } + if (compareFn(value, searchKey) > 0) { continue } diff --git a/src/interpreter/binarySearch.ts b/src/interpreter/binarySearch.ts index 2823011a4..01e7b0735 100644 --- a/src/interpreter/binarySearch.ts +++ b/src/interpreter/binarySearch.ts @@ -18,7 +18,24 @@ const NOT_FOUND = -1 * - orderingDirection - must be set to either 'asc' or 'desc' to indicate the ordering direction for the search range, * - ifNoMatch - must be set to 'returnLowerBound', 'returnUpperBound' or 'returnNotFound' * - * If the search range contains duplicates, returns the last matching value. If no value found in the range satisfies the above, returns -1. + * If the search range contains duplicates, returns the last matching value, with one caveat: in the + * 'returnNotFound' mode, when the range contains both duplicates of the searchKey and interspersed + * empty cells, the search may report any of the duplicated positions (Excel's binary-search modes + * likewise leave this unspecified). + * + * If no value found in the range satisfies the above, returns -1. + * + * Empty cells (EmptyValue) are skipped: they are not treated as ordered values during the + * approximate search. This mirrors Google Sheets and Excel's linear approximate lookups, which + * ignore genuinely empty cells (but not empty strings) when looking for the lower/upper bound; + * for Excel's explicit binary search modes (XLOOKUP search_mode ±2) the result on a range with + * interspersed empty cells is unspecified — see docs/guide/list-of-differences.md. + * The returned offset is always relative to the original range, so empty cells keep their slots + * and the position of the matched non-empty cell is reported unchanged. + * + * Complexity: O(log n) as long as the binary-search descent probes no empty cell — in particular + * for ranges without empty cells. Only when the descent touches an empty cell does the search fall + * back to an O(n) scan that compacts the non-empty indices and re-runs the binary search over them. * * Note: this function does not normalize input strings. */ @@ -39,8 +56,66 @@ export function findLastOccurrenceInOrderedRange( ? (left: RawNoErrorScalarValue, right: RawInterpreterValue) => compare(left, right) : (left: RawNoErrorScalarValue, right: RawInterpreterValue) => -compare(left, right) - const foundIndex = findLastMatchingIndex(index => compareFn(searchKey, getValueFromIndexFn(index)) >= 0, start, end) - const foundValue = getValueFromIndexFn(foundIndex) + /* + * Returns the original index of the first non-empty cell at or after fromIndex, or undefined if + * all the remaining cells are empty. Costs O(gap length), which the search pays only when it + * actually needs to step over empty slots. + */ + const findNextNonEmptyIndex = (fromIndex: number): number | undefined => { + for (let index = fromIndex; index <= end; index++) { + if (getValueFromIndexFn(index) !== EmptyValue) { + return index + } + } + return undefined + } + + // Fast path: binary search directly over the original range, tracking whether the descent ever + // probed an empty cell. Within the sorted-input contract, empty cells are the only source of + // non-monotonicity in the search predicate: compare() ranks EmptyValue below every non-empty + // value, and genuinely empty cells surface as the EmptyValue sentinel — empty strings and 0 do + // not. (Error values also break the ordering, but a range containing errors is outside the + // contract, and the compaction fallback keeps them too.) In a descent that never probes an empty + // cell every probed pivot is a correctly-ordered non-empty value, so each discarded half is + // justified by the monotonicity of the non-empty values, and the landing index — which is always + // probed — is the same one the compacted search below would return: the result can be trusted + // as-is. This keeps the search O(log n) unless an empty cell actually interferes. + let probedEmptyCell = false + const directIndex = findLastMatchingIndex(index => { + const value = getValueFromIndexFn(index) + if (value === EmptyValue) { + probedEmptyCell = true + } + return compareFn(searchKey, value) >= 0 + }, start, end) + + let foundIndex: number + + if (!probedEmptyCell) { + foundIndex = directIndex + } else if (ifNoMatch === 'returnNotFound' && directIndex !== NOT_FOUND && getValueFromIndexFn(directIndex) === searchKey) { + // Exact-match mode: a misdirected descent cannot produce a false positive, because the landing + // value is re-checked for equality here. Accept the hit and skip the O(n) fallback. + return directIndex - start + } else { + // The descent probed an empty cell, so its result cannot be trusted (HF-223): collect the + // original indices of the non-empty cells (O(n)) and re-run the binary search over the + // compacted, empty-free index list. The result maps back to the original index space, so empty + // cells keep their slots and the matched non-empty cell's original position is reported + // unchanged. On an all-empty range the compacted list has no elements and the search reports + // NOT_FOUND, which the ifNoMatch branches below preserve. + const nonEmptyIndices: number[] = [] + for (let index = start; index <= end; index++) { + if (getValueFromIndexFn(index) !== EmptyValue) { + nonEmptyIndices.push(index) + } + } + + const foundCompactedIndex = findLastMatchingIndex(compactedIndex => compareFn(searchKey, getValueFromIndexFn(nonEmptyIndices[compactedIndex])) >= 0, 0, nonEmptyIndices.length - 1) + foundIndex = foundCompactedIndex === NOT_FOUND ? NOT_FOUND : nonEmptyIndices[foundCompactedIndex] + } + + const foundValue = foundIndex === NOT_FOUND ? EmptyValue : getValueFromIndexFn(foundIndex) if (foundValue === searchKey) { return foundIndex - start @@ -48,7 +123,15 @@ export function findLastOccurrenceInOrderedRange( if (ifNoMatch === 'returnLowerBound') { if (foundIndex === NOT_FOUND) { - return orderingDirection === 'asc' ? NOT_FOUND : 0 + if (orderingDirection === 'asc') { + return NOT_FOUND + } + + // orderingDirection === 'desc': the key exceeds every value in the range, so the lower bound + // is the first (largest) non-empty value — never an empty leading cell, and NOT_FOUND on an + // all-empty range. + const firstNonEmptyIndex = findNextNonEmptyIndex(start) + return firstNonEmptyIndex !== undefined ? firstNonEmptyIndex - start : NOT_FOUND } if (typeof foundValue !== typeof searchKey) { @@ -60,14 +143,23 @@ export function findLastOccurrenceInOrderedRange( return foundIndex - start } - // orderingDirection === 'desc' - const nextIndex = foundIndex+1 - return nextIndex <= end ? nextIndex - start : NOT_FOUND + // orderingDirection === 'desc': step to the next non-empty cell, so skipped empty slots never + // shift the reported position. + const nextIndex = findNextNonEmptyIndex(foundIndex + 1) + return nextIndex !== undefined ? nextIndex - start : NOT_FOUND } if (ifNoMatch === 'returnUpperBound') { if (foundIndex === NOT_FOUND) { - return orderingDirection === 'asc' ? 0 : NOT_FOUND + if (orderingDirection === 'desc') { + return NOT_FOUND + } + + // orderingDirection === 'asc': the key precedes every value in the range, so the upper bound + // is the first (smallest) non-empty value — never an empty leading cell, and NOT_FOUND on an + // all-empty range. + const firstNonEmptyIndex = findNextNonEmptyIndex(start) + return firstNonEmptyIndex !== undefined ? firstNonEmptyIndex - start : NOT_FOUND } if (typeof foundValue !== typeof searchKey) { @@ -79,9 +171,10 @@ export function findLastOccurrenceInOrderedRange( return foundIndex - start } - // orderingDirection === 'asc' - const nextIndex = foundIndex+1 - return nextIndex <= end ? nextIndex - start : NOT_FOUND + // orderingDirection === 'asc': step to the next non-empty cell, so skipped empty slots never + // shift the reported position. + const nextIndex = findNextNonEmptyIndex(foundIndex + 1) + return nextIndex !== undefined ? nextIndex - start : NOT_FOUND } // ifNoMatch === 'returnNotFound' diff --git a/src/interpreter/plugin/LookupPlugin.ts b/src/interpreter/plugin/LookupPlugin.ts index 00f942ddf..625a68735 100644 --- a/src/interpreter/plugin/LookupPlugin.ts +++ b/src/interpreter/plugin/LookupPlugin.ts @@ -12,7 +12,7 @@ import { ProcedureAst } from '../../parser' import { StatType } from '../../statistics' import { zeroIfEmpty } from '../ArithmeticHelper' import { InterpreterState } from '../InterpreterState' -import { InternalScalarValue, InterpreterValue, RawNoErrorScalarValue } from '../InterpreterValue' +import { EmptyValue, InternalScalarValue, InterpreterValue, RawNoErrorScalarValue } from '../InterpreterValue' import { SimpleRangeValue } from '../../SimpleRangeValue' import { FunctionArgumentType, FunctionPlugin, FunctionPluginTypecheck, ImplementedFunctions } from './FunctionPlugin' import { ArraySize } from '../../ArraySize' @@ -237,7 +237,7 @@ export class LookupPlugin extends FunctionPlugin implements FunctionPluginTypech if (value instanceof SimpleRangeValue) { return new CellError(ErrorType.VALUE, ErrorMessage.WrongType) } - return value + return this.zeroIfEmptyResult(value) } private doHlookup(key: RawNoErrorScalarValue, rangeValue: SimpleRangeValue, index: number, searchOptions: SearchOptions): InternalScalarValue { @@ -265,7 +265,18 @@ export class LookupPlugin extends FunctionPlugin implements FunctionPluginTypech if (value instanceof SimpleRangeValue) { return new CellError(ErrorType.VALUE, ErrorMessage.WrongType) } - return value + return this.zeroIfEmptyResult(value) + } + + /** + * Excel returns 0 (not blank) for a matched cell that is empty — a reference to an empty cell coerces + * to 0 in Excel. Mirror that on the lookup RETURN value so VLOOKUP/HLOOKUP/XLOOKUP match Excel. + * + * Same rule as `ArithmeticHelper.zeroIfEmpty`, kept as a separate method because the lookup RETURN + * value is an `InternalScalarValue` (wider than that helper's `RawNoErrorScalarValue` parameter). + */ + private zeroIfEmptyResult(value: InternalScalarValue): InternalScalarValue { + return value === EmptyValue ? 0 : value } private doXlookup(key: RawNoErrorScalarValue, lookupRange: SimpleRangeValue, returnRange: SimpleRangeValue, notFoundFlag: any, isWildcardMatchMode: boolean, searchOptions: SearchOptions): InterpreterValue { @@ -284,7 +295,8 @@ export class LookupPlugin extends FunctionPlugin implements FunctionPluginTypech } const returnValues: InternalScalarValue[][] = isVerticalSearch ? [returnRange.data[indexFound]] : returnRange.data.map((row) => [row[indexFound]]) - return SimpleRangeValue.onlyValues(returnValues) + const coerced = returnValues.map((row) => row.map((value) => this.zeroIfEmptyResult(value))) + return SimpleRangeValue.onlyValues(coerced) } private doMatch(key: RawNoErrorScalarValue, rangeValue: SimpleRangeValue, type: number): InternalScalarValue { From 6e87a379088a7639eb3ebd9623b68792a10a1cc0 Mon Sep 17 00:00:00 2001 From: GreenFlux <24459976+GreenFlux@users.noreply.github.com> Date: Wed, 22 Jul 2026 07:18:43 -0400 Subject: [PATCH 20/28] docs(guide): update support page (HF-128) (#1712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Context Updates the Support guide page (`docs/guide/support.md`) to match the new support content defined in [HF-128](https://app.clickup.com/t/9015210959/HF-128). Changes: - **Community support** now points developers to the [community forum](https://forum.handsontable.com/) for questions/feature requests and to the [bug report issue template](https://github.com/handsontable/hyperformula/issues/new?template=bug_report.yaml) for bugs. - **Premium support** describes the three support tiers (Standard, Priority, Enterprise) in a comparison table, with a link to the full plan details. - **Consulting services** section retained. ### How did you test your changes? Documentation-only change. Verified the referenced `bug_report.yaml` issue template exists in `.github/ISSUE_TEMPLATE/` and kept the internal "Contact sales" link in the existing relative `contact.md` form. ### Types of changes - [ ] Breaking change (a fix or a feature because of which an existing functionality doesn't work as expected anymore) - [ ] New feature or improvement (a non-breaking change that adds functionality) - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] Additional language file, or a change to an existing language file (translations) - [x] Change to the documentation ### Related issues: 1. HF-128 ### Checklist: - [x] I have reviewed the guidelines about Contributing to HyperFormula and I confirm that my code follows the code style of this project. - [x] My changes require a documentation update. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- > [!NOTE] > **Low Risk** > Documentation-only navigation and link changes with no runtime or API impact. > > **Overview** > **Removes the in-docs Support guide** (`docs/guide/support.md`) and drops it from the VuePress **About** sidebar in `docs/.vuepress/config.js`. > > **Support CTAs now go off-site:** the “Looking for technical support?” section in `contact.md` and the support link at the end of `quality.md` no longer point to `support.md`; they link to `https://hyperformula.handsontable.com/#pricing` instead. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 8dc15dd5f82676eeecc086bed8f3b73cfdf118c2. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Claude Opus 4.8 Co-authored-by: Kuba Sekowski --- docs/.vuepress/config.js | 1 - docs/guide/contact.md | 2 +- docs/guide/quality.md | 2 +- docs/guide/support.md | 32 -------------------------------- 4 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 docs/guide/support.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 8971d8f5b..928edba20 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -298,7 +298,6 @@ module.exports = { ['/guide/supported-browsers', 'Supported browsers'], ['/guide/dependencies', 'Dependencies'], ['/guide/licensing', 'Licensing'], - ['/guide/support', 'Support'], ] }, { diff --git a/docs/guide/contact.md b/docs/guide/contact.md index 496b3a6c5..95136d824 100644 --- a/docs/guide/contact.md +++ b/docs/guide/contact.md @@ -21,4 +21,4 @@ or submit your inquiry through the ## Looking for technical support? -Visit the page about [Support](support.md) \ No newline at end of file +[Learn more about support options →](https://hyperformula.handsontable.com/#pricing) \ No newline at end of file diff --git a/docs/guide/quality.md b/docs/guide/quality.md index 8099eda93..ad0a25920 100644 --- a/docs/guide/quality.md +++ b/docs/guide/quality.md @@ -87,4 +87,4 @@ HyperFormula offers comprehensive support options to ensure your success: Our expert team has been supporting enterprises since 2012 and understands how to respond to individual business needs. -[Learn more about support options →](support.md) +[Learn more about support options →](https://hyperformula.handsontable.com/#pricing) diff --git a/docs/guide/support.md b/docs/guide/support.md deleted file mode 100644 index 1f3c627b5..000000000 --- a/docs/guide/support.md +++ /dev/null @@ -1,32 +0,0 @@ -# Support - -## Community support - -If you have an issue or a question, you can use GitHub to ask the -community for help. - -[Create an issue](https://github.com/handsontable/hyperformula/issues/new/choose) - -## Premium support - -We provide support to companies using HyperFormula to enrich their -mission-critical applications. We can help you at every stage of -your development. Our experts have been supporting enterprises since -2012 and know exactly how to respond to individual needs. - -[Contact sales](contact.md) - -## Consulting services - -If you need an instant turn-key solution, then consulting services -are the quickest way to get it done. Along with our technical partners, -we can help you in many ways. For instance: - -* Build a proof of concept -* Develop deployment strategies -* Implement new features -* Improve existing features -* Provide a tailored training program -* Provide technical leadership - -[Contact sales](contact.md) From fbb471083f1f8c89ce7542fce67b953fc5a651f1 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Thu, 23 Jul 2026 15:59:15 +0200 Subject: [PATCH 21/28] feat: implement SORT dynamic array function (HF-69) (#1707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What & why Implements **SORT** (`HF-69`, child of HF-28 "Modern dynamic array functions", sibling of the shipped SEQUENCE and of VSTACK/HSTACK). Adds `SORT(array, [sort_index], [sort_order], [by_col])` as a dynamic array function. Tests: handsontable/hyperformula-tests#24 (paired). Sibling PR: #1708 (UNIQUE / HF-68). ADR: `docs/adr/2026-07-13-sort-unique-array-functions.md`. ## Behavior - Returns an array the **same shape** as the input. - `sort_index` (default 1): 1-based index into the sort dimension. - `sort_order`: `1` ascending (default) or `-1` descending. - `by_col`: `FALSE` (default) reorders rows; `TRUE` reorders columns. - Ordering reuses `ArithmeticHelper` (mixed types: numbers < text < logical; empties; locale collation via `caseSensitive`/`accentSensitive`) and is **stable** — ties keep input order. ## Design Mirrors the SEQUENCE/FILTER machinery: `sizeOfResultArrayMethod` + `vectorizationForbidden: true`, runtime via `runFunction` returning `SimpleRangeValue`/`CellError`, parse-time size method returning a **fresh** `ArraySize` (the input's `isRef` flag is dropped — a ref-flagged size is treated as scalar and would collapse the spill). ## Notes — divergences from Excel (surfaced here + inline + in tests) - **`sort_order` is strictly `{1, -1}`**; any other value → `#VALUE!`. Excel documents only `{1,-1}`; the reported "`sort_order=0` does not error" quirk is undocumented and **could not be re-verified against live Excel in this environment**, so the strict documented contract was chosen (see ADR `dec_2`, `con_1`). Flagged for live-Excel/Kuba confirmation. - **Multi-key array-constant `sort_index`** (e.g. `{1,2}`) is **not supported in v1** (documented in `known-limitations.md`; ADR `dec_6`). - In-range errors propagate (first error found; ADR `dec_7`). ## Error-type map `sort_order ∉ {1,-1}` → `#VALUE!` (BadMode) · `sort_index < 1` → `#VALUE!` (LessThanOne) · `sort_index >` dimension → `#VALUE!` (ValueLarge) · in-range error → propagate · wrong arity → `#N/A`. ## Definition of Done - [x] Production code (`SortPlugin.ts`, registered via `plugin/index.ts`) - [x] i18n — all 17 language packs (authoritative MS Functions Translator names; enUS inherits enGB) - [x] Tests (paired tests PR) — across the standard array-function groups, dual-env safe - [x] Docs — `built-in-functions.md`, `known-limitations.md` - [x] JSDoc on all methods - [x] CHANGELOG entry - [x] ADR with audit-verified citations Source: https://app.clickup.com/t/86c89q1tt --- > [!NOTE] > **Low Risk** > Self-contained new array function following existing SEQUENCE/FILTER patterns; no changes to auth, persistence, or core recalculation beyond registering one plugin. > > **Overview** > Adds the **SORT** dynamic array function: `SORT(Array, [SortIndex], [SortOrder], [ByCol])` returns the input range reordered by row (default) or column, same dimensions as the source. > > Implementation lives in new `SortPlugin.ts`, wired like other array functions (`sizeOfResultArrayMethod`, `vectorizationForbidden`, spill size copied from input without propagating `isRef`). Sort keys use `ArithmeticHelper` (with empty cells forced last); invalid `sort_order` (not `1` or `-1`), bad `sort_index`, in-range errors, and empty ranges get the documented `#VALUE!` / `#N/A` / error propagation behavior. > > Docs and changelog are updated; **known-limitations** documents single-key only, strict sort order, and HF comparison rules. **SORT** is added to all 17 language packs. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f081be57e9651d13d7228fa33eea3a75ab4aaa28. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + docs/guide/built-in-functions.md | 1 + docs/guide/known-limitations.md | 8 ++ src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/idID.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/SortPlugin.ts | 162 +++++++++++++++++++++++++++ src/interpreter/plugin/index.ts | 1 + 22 files changed, 190 insertions(+) create mode 100644 src/interpreter/plugin/SortPlugin.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 94424f8a3..fab7315b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added new functions: VSTACK, HSTACK. [#1698](https://github.com/handsontable/hyperformula/pull/1698) - Added a new function: `XIRR`. [#1701](https://github.com/handsontable/hyperformula/pull/1701) +- Added the SORT function. [#1707](https://github.com/handsontable/hyperformula/pull/1707) - Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674) - Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145) diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index d486c8e04..a322bca16 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -67,6 +67,7 @@ Total number of functions: **{{ $page.functionsCount }}** | SEQUENCE | Returns an array of sequential numbers. | SEQUENCE(Rows, [Cols], [Start], [Step]) | | VSTACK | Stacks arrays vertically into a single array. | VSTACK(Array1, [Array2], ...[ArrayN]) | | HSTACK | Stacks arrays horizontally into a single array. | HSTACK(Array1, [Array2], ...[ArrayN]) | +| SORT | Sorts the rows or columns of an array. | SORT(Array, [SortIndex], [SortOrder], [ByCol]) | ### Date and time diff --git a/docs/guide/known-limitations.md b/docs/guide/known-limitations.md index c4a1bbc61..76a56c3d7 100644 --- a/docs/guide/known-limitations.md +++ b/docs/guide/known-limitations.md @@ -38,6 +38,14 @@ a circular reference. * Array-producing functions (e.g., SEQUENCE, FILTER) require their output dimensions to be determinable at parse time. Passing cell references or formulas as dimension arguments (e.g., `=SEQUENCE(A1)`) results in a `#VALUE!` error, because the output size cannot be resolved before evaluation. * The TEXT function does not accept embedded double-quote literals in the format string. In Excel, `""` inside a format string is an escape sequence for a literal `"` character — e.g. `=TEXT(1234.5, "#,##0.00 ""zł""")` returns `"1,234.50 zł"`. If your application requires this escape sequence, supply a custom [`stringifyCurrency`](currency-handling.md) callback. +### SORT function + +* The `SortIndex` argument accepts a single key only. Multi-key sorting through an array constant (for example `=SORT(A1:B9, {1,2})`) is not supported; sort by one column or row at a time. + +* The `SortOrder` argument must be exactly `1` (ascending) or `-1` (descending). Any other value returns a `#VALUE!` error. + +* Ordering (including mixed types, empty cells, and text collation) follows HyperFormula's own comparison rules, which honor the `caseSensitive` and `accentSensitive` configuration options. Numbers sort before text, and text before logical values. + ### OFFSET function HyperFormula resolves the OFFSET function at parse time rather than during evaluation. The parser inspects the arguments and rewrites the expression into a plain cell reference or range. This keeps the dependency graph accurate but imposes several restrictions. diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index ff9649112..9e035a881 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'ODPIS.LIN', + SORT: 'SORT', SPLIT: 'SPLIT', SQRT: 'ODMOCNINA', STDEVA: 'STDEVA', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 6a44e95ce..6a9e78f7d 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'LA', + SORT: 'SORTER', SPLIT: 'SPLIT', SQRT: 'KVROD', STDEVA: 'STDAFVV', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 7633eb14f..1becb9679 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINHYP', SLN: 'LIA', + SORT: 'SORTIEREN', SPLIT: 'SPLIT', SQRT: 'WURZEL', STDEVA: 'STABWA', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 7011a8162..0350eb314 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -210,6 +210,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'SLN', + SORT: 'SORT', SPLIT: 'SPLIT', SQRT: 'SQRT', STDEVA: 'STDEVA', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 4d3a8217f..f446ad063 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -208,6 +208,7 @@ export const dictionary: RawTranslationPackage = { SIN: 'SENO', SINH: 'SENOH', SLN: 'SLN', + SORT: 'ORDENAR', SPLIT: 'SPLIT', SQRT: 'RAIZ', STDEVA: 'DESVESTA', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 55f13bd03..554b12e33 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'STP', + SORT: 'LAJITTELE', SPLIT: 'SPLIT', SQRT: 'NELIÖJUURI', STDEVA: 'KESKIHAJONTAA', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index de69b12ec..81bcdd779 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'AMORLIN', + SORT: 'TRIER', SPLIT: 'SPLIT', SQRT: 'RACINE', STDEVA: 'STDEVA', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 200787a5e..4d429523a 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'LCSA', + SORT: 'SORBA.RENDEZ', SPLIT: 'SPLIT', SQRT: 'GYÖK', STDEVA: 'SZÓRÁSA', diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts index 72e69bb05..e5c8723cb 100644 --- a/src/i18n/languages/idID.ts +++ b/src/i18n/languages/idID.ts @@ -210,6 +210,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'GSL', + SORT: 'SORT', SPLIT: 'PISAH', SQRT: 'AKAR', STDEVA: 'STDEVA', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 93a6f9a69..210047149 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SEN', SINH: 'SENH', SLN: 'AMMORT.COST', + SORT: 'DATI.ORDINA', SPLIT: 'SPLIT', SQRT: 'RADQ', STDEVA: 'DEV.ST.VALORI', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index a0c9b9e47..fcfa50c8f 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'LINAVS', + SORT: 'SORTER', SPLIT: 'SPLIT', SQRT: 'ROT', STDEVA: 'STDAVVIKA', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index f8c1eed96..fcc0640c4 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'LIN.AFSCHR', + SORT: 'SORTEREN', SPLIT: 'SPLIT', SQRT: 'WORTEL', STDEVA: 'STDEVA', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index b6b8aa5ec..8b42176d3 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'SLN', + SORT: 'SORTUJ', SPLIT: 'PODZIEL.TEKST', SQRT: 'PIERWIASTEK', STDEVA: 'ODCH.STANDARDOWE.A', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 3d9386d9f..1d478543a 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SEN', SINH: 'SENH', SLN: 'DPD', + SORT: 'ORDENAR', SPLIT: 'SPLIT', SQRT: 'RAIZ', STDEVA: 'DESVPADA', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index c247b3050..856d94083 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'АПЛ', + SORT: 'СОРТ', SPLIT: 'SPLIT', SQRT: 'КОРЕНЬ', STDEVA: 'СТАНДОТКЛОНА', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 13fae5523..ad27924ee 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SIN', SINH: 'SINH', SLN: 'LINAVSKR', + SORT: 'SORTERA', SPLIT: 'SPLIT', SQRT: 'ROT', STDEVA: 'STDEVA', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index 8196edc45..c19796ed6 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -208,6 +208,7 @@ const dictionary: RawTranslationPackage = { SIN: 'SİN', SINH: 'SINH', SLN: 'DA', + SORT: 'SIRALA', SPLIT: 'SPLIT', SQRT: 'KAREKÖK', STDEVA: 'STDSAPMAA', diff --git a/src/interpreter/plugin/SortPlugin.ts b/src/interpreter/plugin/SortPlugin.ts new file mode 100644 index 000000000..088bd275e --- /dev/null +++ b/src/interpreter/plugin/SortPlugin.ts @@ -0,0 +1,162 @@ +/** + * @license + * Copyright (c) 2025 Handsoncode. All rights reserved. + */ + +import {ArraySize} from '../../ArraySize' +import {CellError, ErrorType} from '../../Cell' +import {ErrorMessage} from '../../error-message' +import {ProcedureAst} from '../../parser' +import {InterpreterState} from '../InterpreterState' +import {EmptyValue, InternalNoErrorScalarValue, InternalScalarValue, InterpreterValue} from '../InterpreterValue' +import {SimpleRangeValue} from '../../SimpleRangeValue' +import {FunctionArgumentType, FunctionPlugin, FunctionPluginTypecheck, ImplementedFunctions} from './FunctionPlugin' + +/** + * Plugin implementing the SORT spreadsheet function. + * + * SORT(array, [sort_index], [sort_order], [by_col]) returns the elements of + * `array` sorted along one dimension. The returned array has the same shape as + * the input. By default it reorders rows using the first column as the key, + * ascending. Ordering is delegated to {@link ArithmeticHelper} so that mixed + * types, empty cells, and locale collation behave exactly as elsewhere in the + * engine. + */ +export class SortPlugin extends FunctionPlugin implements FunctionPluginTypecheck { + public static implementedFunctions: ImplementedFunctions = { + 'SORT': { + method: 'sort', + sizeOfResultArrayMethod: 'sortArraySize', + enableArrayArithmeticForArguments: true, + parameters: [ + {argumentType: FunctionArgumentType.RANGE}, + {argumentType: FunctionArgumentType.NUMBER, defaultValue: 1, emptyAsDefault: true}, + {argumentType: FunctionArgumentType.NUMBER, defaultValue: 1, emptyAsDefault: true}, + {argumentType: FunctionArgumentType.BOOLEAN, defaultValue: false, emptyAsDefault: true}, + ], + vectorizationForbidden: true, + }, + } + + /** + * Corresponds to SORT(array, [sort_index], [sort_order], [by_col]). + * + * `sort_order` is validated strictly to {1, -1} (the documented contract); + * any other value yields #VALUE!. `sort_index` is a 1-based index into the sort + * dimension (columns when sorting rows, rows when `by_col` is TRUE). Errors found + * anywhere in the input range are propagated. + * + * @param {ProcedureAst} ast - the parsed function-call AST node. + * @param {InterpreterState} state - current interpreter evaluation state. + */ + public sort(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('SORT'), + (range: SimpleRangeValue, sortIndex: number, sortOrder: number, byCol: boolean) => { + if (sortOrder !== 1 && sortOrder !== -1) { + return new CellError(ErrorType.VALUE, ErrorMessage.BadMode) + } + + const data = range.data + const height = range.height() + const width = range.width() + + const firstError = SortPlugin.findFirstError(data) + if (firstError !== undefined) { + return firstError + } + + // An empty input range (e.g. a whole-column reference to an empty sheet) + // yields no rows/columns to sort. Return #N/A rather than letting the empty + // 2-D array reach SimpleRangeValue.onlyValues, which reads data[0].length and + // would throw. Mirrors UNIQUE/FILTER's empty-range handling. + if (data.length === 0 || data[0].length === 0) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyRange) + } + + const index = Math.trunc(sortIndex) + const sortDimension = byCol ? height : width + if (index < 1) { + return new CellError(ErrorType.VALUE, ErrorMessage.LessThanOne) + } + if (index > sortDimension) { + return new CellError(ErrorType.VALUE, ErrorMessage.ValueLarge) + } + + const keyIndex = index - 1 + const compare = (a: InternalNoErrorScalarValue, b: InternalNoErrorScalarValue): number => { + // Excel keeps empty cells at the end of the result regardless of + // sort_order, rather than coercing them to 0 and ordering by value. + // Force empties last (direction-independent) before delegating the + // rest to ArithmeticHelper. + const aEmpty = a === EmptyValue + const bEmpty = b === EmptyValue + if (aEmpty || bEmpty) { + if (aEmpty && bEmpty) { + return 0 + } + return aEmpty ? 1 : -1 + } + if (this.arithmeticHelper.lt(a, b)) { + return -sortOrder + } + if (this.arithmeticHelper.gt(a, b)) { + return sortOrder + } + return 0 + } + + if (byCol) { + // Reorder columns; the key of column c is data[keyIndex][c]. + const order = Array.from({length: width}, (_, c) => c) + order.sort((c1, c2) => compare( + data[keyIndex][c1] as InternalNoErrorScalarValue, + data[keyIndex][c2] as InternalNoErrorScalarValue, + )) + const result: InternalScalarValue[][] = data.map(row => order.map(c => row[c])) + return SimpleRangeValue.onlyValues(result) + } + + // Reorder rows; the key of row r is data[r][keyIndex]. + const rows: InternalScalarValue[][] = data.map(row => row.slice()) + rows.sort((r1, r2) => compare( + r1[keyIndex] as InternalNoErrorScalarValue, + r2[keyIndex] as InternalNoErrorScalarValue, + )) + return SimpleRangeValue.onlyValues(rows) + } + ) + } + + /** + * Predicts the output array size for SORT at parse time. + * The result is always the same shape as the input array. + * + * @param {ProcedureAst} ast - the parsed function-call AST node. + * @param {InterpreterState} state - current interpreter evaluation state. + */ + public sortArraySize(ast: ProcedureAst, state: InterpreterState): ArraySize { + if (ast.args.length < 1 || ast.args.length > 4) { + return ArraySize.error() + } + const metadata = this.metadata('SORT') + const subChecks = ast.args.map((arg) => + this.arraySizeForAst(arg, new InterpreterState(state.formulaAddress, state.arraysFlag || (metadata?.enableArrayArithmeticForArguments ?? false)))) + // Return a fresh ArraySize (isRef defaults to false). Propagating the input's + // ArraySize verbatim would carry its `isRef` flag, and ArraySize.isScalar() + // treats a ref as scalar — which would collapse the spilled result into a + // single cell. SORT's output always matches the input shape. + return new ArraySize(subChecks[0].width, subChecks[0].height) + } + + /** Returns the first {@link CellError} found in a 2-D array, or undefined. */ + private static findFirstError(data: InternalScalarValue[][]): CellError | undefined { + for (const row of data) { + for (const cell of row) { + if (cell instanceof CellError) { + return cell + } + } + } + return undefined + } +} diff --git a/src/interpreter/plugin/index.ts b/src/interpreter/plugin/index.ts index e86ba4f2a..a390ad843 100644 --- a/src/interpreter/plugin/index.ts +++ b/src/interpreter/plugin/index.ts @@ -36,6 +36,7 @@ export {RadiansPlugin} from './RadiansPlugin' export {RadixConversionPlugin} from './RadixConversionPlugin' export {RandomPlugin} from './RandomPlugin' export {SequencePlugin} from './SequencePlugin' +export {SortPlugin} from './SortPlugin' export {RoundingPlugin} from './RoundingPlugin' export {SqrtPlugin} from './SqrtPlugin' export {ConditionalAggregationPlugin} from './ConditionalAggregationPlugin' From 23a7437aa8e3e375bbdcafea09e905a25cbeb53c Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Thu, 23 Jul 2026 16:02:05 +0200 Subject: [PATCH 22/28] feat: implement UNIQUE dynamic array function (HF-68) (#1708) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What & why Implements **UNIQUE** (`HF-68`, child of HF-28 "Modern dynamic array functions", sibling of the shipped SEQUENCE and of VSTACK/HSTACK). Adds `UNIQUE(array, [by_col], [exactly_once])` as a dynamic array function. Tests: handsontable/hyperformula-tests#25 (paired). Sibling PR: #1707 (SORT / HF-69). ADR: `docs/adr/2026-07-13-sort-unique-array-functions.md`. ## Behavior - Returns the **distinct rows** (or columns when `by_col` is `TRUE`) of the input, preserving first-occurrence order. - `by_col`: `FALSE` (default) compares rows; `TRUE` compares columns. - `exactly_once`: `TRUE` returns only rows/columns occurring exactly once; `FALSE` (default) returns all distinct. - Equality reuses `ArithmeticHelper.eq` → **case-insensitive by default** (honors `caseSensitive`), matching Excel's UNIQUE. - Result size is data-dependent; mirrors FILTER (predict input size as upper bound, return the smaller actual result). ## Design Mirrors the FILTER machinery for dynamic-size results: `sizeOfResultArrayMethod` + `vectorizationForbidden: true`, runtime via `runFunction`, parse-time size method returning a **fresh** `ArraySize` (drops the input's `isRef` flag). Deduplication is O(n²) in the number of vectors because locale-aware equality is not trivially hashable — noted in code; acceptable for v1. ## Notes — divergences from Excel (surfaced here + inline + in tests) - **Empty result** (only via `exactly_once` when nothing occurs exactly once) → `#N/A`. Excel returns `#CALC!`, which HyperFormula has no type for; mirrors FILTER's empty-result mapping (ADR `dec_8`). - Comparison honors HF's collation config rather than a byte-for-byte Excel oracle (no live Excel in this environment; ADR `con_1`). - In-range errors propagate (first error found; ADR `dec_7`). ## Definition of Done - [x] Production code (`UniquePlugin.ts`, registered via `plugin/index.ts`) - [x] i18n — all 17 language packs (authoritative MS Functions Translator names; enUS inherits enGB) - [x] Tests (paired tests PR) — across the standard array-function groups, dual-env safe - [x] Docs — `built-in-functions.md`, `known-limitations.md` - [x] JSDoc on all methods - [x] CHANGELOG entry - [x] ADR with audit-verified citations Source: https://app.clickup.com/t/86c89q1tq --- > [!NOTE] > **Low Risk** > Additive array function behind existing dynamic-array machinery; no changes to auth, persistence, or core evaluation paths beyond new plugin registration. > > **Overview** > Adds the Excel-style **`UNIQUE(array, [ByCol], [ExactlyOnce])`** dynamic array function so formulas can return distinct rows or columns with first-occurrence order preserved. > > **`UniquePlugin`** implements deduplication via `ArithmeticHelper.eq` (honors `caseSensitive` / `accentSensitive`), optional column-wise mode and “exactly once” filtering, propagates the first in-range error, and returns **`#N/A`** when `ExactlyOnce` would yield an empty result (aligned with FILTER). Spill sizing follows FILTER: parse-time upper bound from input dimensions, `vectorizationForbidden: true`, and a fresh `ArraySize` so `isRef` is not carried through. > > Also registers the plugin, adds **`UNIQUE`** to all language packs, documents the function and known limitations, and records the change in the changelog. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a4097a44a1c3a8286d1ea590c43746a05d070194. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: Kuba Sekowski --- CHANGELOG.md | 1 + docs/guide/built-in-functions.md | 1 + docs/guide/known-limitations.md | 5 + src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/idID.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/UniquePlugin.ts | 157 +++++++++++++++++++++++++ src/interpreter/plugin/index.ts | 1 + 22 files changed, 182 insertions(+) create mode 100644 src/interpreter/plugin/UniquePlugin.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fab7315b4..af942bd4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Added new functions: VSTACK, HSTACK. [#1698](https://github.com/handsontable/hyperformula/pull/1698) - Added a new function: `XIRR`. [#1701](https://github.com/handsontable/hyperformula/pull/1701) +- Added the UNIQUE function. [#1708](https://github.com/handsontable/hyperformula/pull/1708) - Added the SORT function. [#1707](https://github.com/handsontable/hyperformula/pull/1707) - Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674) - Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145) diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index a322bca16..51846e097 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -67,6 +67,7 @@ Total number of functions: **{{ $page.functionsCount }}** | SEQUENCE | Returns an array of sequential numbers. | SEQUENCE(Rows, [Cols], [Start], [Step]) | | VSTACK | Stacks arrays vertically into a single array. | VSTACK(Array1, [Array2], ...[ArrayN]) | | HSTACK | Stacks arrays horizontally into a single array. | HSTACK(Array1, [Array2], ...[ArrayN]) | +| UNIQUE | Returns the unique rows or columns of an array. | UNIQUE(Array, [ByCol], [ExactlyOnce]) | | SORT | Sorts the rows or columns of an array. | SORT(Array, [SortIndex], [SortOrder], [ByCol]) | ### Date and time diff --git a/docs/guide/known-limitations.md b/docs/guide/known-limitations.md index 76a56c3d7..7f0e2361d 100644 --- a/docs/guide/known-limitations.md +++ b/docs/guide/known-limitations.md @@ -38,6 +38,11 @@ a circular reference. * Array-producing functions (e.g., SEQUENCE, FILTER) require their output dimensions to be determinable at parse time. Passing cell references or formulas as dimension arguments (e.g., `=SEQUENCE(A1)`) results in a `#VALUE!` error, because the output size cannot be resolved before evaluation. * The TEXT function does not accept embedded double-quote literals in the format string. In Excel, `""` inside a format string is an escape sequence for a literal `"` character — e.g. `=TEXT(1234.5, "#,##0.00 ""zł""")` returns `"1,234.50 zł"`. If your application requires this escape sequence, supply a custom [`stringifyCurrency`](currency-handling.md) callback. +### UNIQUE function + +* Comparison of values follows HyperFormula's own equality rules, which honor the `caseSensitive` and `accentSensitive` configuration options. By default comparison is case-insensitive. + +* When `ExactlyOnce` is TRUE and no row or column occurs exactly once, `UNIQUE` returns a `#N/A` error (the result would otherwise be empty). ### SORT function * The `SortIndex` argument accepts a single key only. Multi-key sorting through an array constant (for example `=SORT(A1:B9, {1,2})`) is not supported; sort by one column or row at a time. diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index 9e035a881..830b1c196 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'USEKNOUT', UNICHAR: 'UNICHAR', UNICODE: 'UNICODE', + UNIQUE: 'UNIQUE', UPPER: 'VELKÁ', VALUE: 'HODNOTA', VARA: 'VARA', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index 6a9e78f7d..90296fd42 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'AFKORT', UNICHAR: 'UNICHAR', UNICODE: 'UNICODE', + UNIQUE: 'ENTYDIGE', UPPER: 'STORE.BOGSTAVER', VALUE: 'VÆRDI', VARA: 'VARIANSV', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 1becb9679..daaaa0bf7 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'KÜRZEN', UNICHAR: 'UNIZEICHEN', UNICODE: 'UNICODE', + UNIQUE: 'EINDEUTIG', UPPER: 'GROSS', VALUE: 'WERT', VARA: 'VARIANZA', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index 0350eb314..d271b732c 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -243,6 +243,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'TRUNC', UNICHAR: 'UNICHAR', UNICODE: 'UNICODE', + UNIQUE: 'UNIQUE', UPPER: 'UPPER', VALUE: 'VALUE', VARA: 'VARA', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index f446ad063..6fea52e4e 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -241,6 +241,7 @@ export const dictionary: RawTranslationPackage = { TRUNC: 'TRUNCAR', UNICHAR: 'UNICHAR', UNICODE: 'UNICODE', + UNIQUE: 'UNICOS', UPPER: 'MAYUSC', VALUE: 'VALOR', VARA: 'VARA', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 554b12e33..a3b319af2 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'KATKAISE', UNICHAR: 'UNICODEMERKKI', UNICODE: 'UNICODE', + UNIQUE: 'AINUTKERTAISET.ARVOT', UPPER: 'ISOT', VALUE: 'ARVO', VARA: 'VARA', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index 81bcdd779..a29109053 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'TRONQUE', UNICHAR: 'UNICAR', UNICODE: 'UNICODE', + UNIQUE: 'UNIQUE', UPPER: 'MAJUSCULE', VALUE: 'CNUM', VARA: 'VARA', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index 4d429523a..fafc2c4b9 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'CSONK', UNICHAR: 'UNIKARAKTER', UNICODE: 'UNICODE', + UNIQUE: 'EGYEDI', UPPER: 'NAGYBETŰS', VALUE: 'ÉRTÉK', VARA: 'VARA', diff --git a/src/i18n/languages/idID.ts b/src/i18n/languages/idID.ts index e5c8723cb..719cb11db 100644 --- a/src/i18n/languages/idID.ts +++ b/src/i18n/languages/idID.ts @@ -243,6 +243,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'POTONG', UNICHAR: 'UNIKARAKTER', UNICODE: 'UNICODE', + UNIQUE: 'UNIQUE', UPPER: 'HURUF.BESAR', VALUE: 'NILAI', VARA: 'VARA', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 210047149..40ec9cf41 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'TRONCA', UNICHAR: 'CARATT.UNI', UNICODE: 'UNICODE', + UNIQUE: 'UNICI', UPPER: 'MAIUSC', VALUE: 'VALORE', VARA: 'VAR.VALORI', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index fcfa50c8f..89c0f85f7 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'AVKORT', UNICHAR: 'UNICODETEGN', UNICODE: 'UNICODE', + UNIQUE: 'UNIK', UPPER: 'STORE', VALUE: 'VERDI', VARA: 'VARIANSA', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index fcc0640c4..73ef99687 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'GEHEEL', UNICHAR: 'UNITEKEN', UNICODE: 'UNICODE', + UNIQUE: 'UNIEK', UPPER: 'HOOFDLETTERS', VALUE: 'WAARDE', VARA: 'VARA', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index 8b42176d3..003b0fd64 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'LICZBA.CAŁK', UNICHAR: 'ZNAK.UNICODE', UNICODE: 'UNICODE', + UNIQUE: 'UNIKATOWE', UPPER: 'LITERY.WIELKIE', VALUE: 'WARTOŚĆ', VARA: 'WARIANCJA.A', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 1d478543a..408d7f772 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'TRUNCAR', UNICHAR: 'CARACTUNI', UNICODE: 'UNICODE', + UNIQUE: 'EXCLUSIVOS', UPPER: 'MAIÚSCULA', VALUE: 'VALOR', VARA: 'VARA', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 856d94083..657b0db37 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'ОТБР', UNICHAR: 'ЮНИСИМВ', UNICODE: 'UNICODE', + UNIQUE: 'УНИК', UPPER: 'ПРОПИСН', VALUE: 'ЗНАЧ', VARA: 'ДИСПА', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index ad27924ee..5f8758008 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'AVKORTA', UNICHAR: 'UNICHAR', UNICODE: 'UNICODE', + UNIQUE: 'UNIK', UPPER: 'VERSALER', VALUE: 'TEXTNUM', VARA: 'VARA', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index c19796ed6..dc243600d 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -241,6 +241,7 @@ const dictionary: RawTranslationPackage = { TRUNC: 'NSAT', UNICHAR: 'UNICODEKARAKTERİ', UNICODE: 'UNICODE', + UNIQUE: 'BENZERSİZ', UPPER: 'BÜYÜKHARF', VALUE: 'DEĞER', VARA: 'VARA', diff --git a/src/interpreter/plugin/UniquePlugin.ts b/src/interpreter/plugin/UniquePlugin.ts new file mode 100644 index 000000000..5db1fb510 --- /dev/null +++ b/src/interpreter/plugin/UniquePlugin.ts @@ -0,0 +1,157 @@ +/** + * @license + * Copyright (c) 2025 Handsoncode. All rights reserved. + */ + +import {ArraySize} from '../../ArraySize' +import {CellError, ErrorType} from '../../Cell' +import {ErrorMessage} from '../../error-message' +import {ProcedureAst} from '../../parser' +import {InterpreterState} from '../InterpreterState' +import {InternalNoErrorScalarValue, InternalScalarValue, InterpreterValue} from '../InterpreterValue' +import {SimpleRangeValue} from '../../SimpleRangeValue' +import {FunctionArgumentType, FunctionPlugin, FunctionPluginTypecheck, ImplementedFunctions} from './FunctionPlugin' + +/** + * Plugin implementing the UNIQUE spreadsheet function. + * + * UNIQUE(array, [by_col], [exactly_once]) returns the distinct rows (or columns + * when by_col is TRUE) of `array`, preserving first-occurrence order. When + * exactly_once is TRUE, only rows/columns that occur exactly once are returned. + * Equality delegates to {@link ArithmeticHelper}, so comparison honors the + * caseSensitive/accentSensitive configuration (case-insensitive by default). + */ +export class UniquePlugin extends FunctionPlugin implements FunctionPluginTypecheck { + public static implementedFunctions: ImplementedFunctions = { + 'UNIQUE': { + method: 'unique', + sizeOfResultArrayMethod: 'uniqueArraySize', + enableArrayArithmeticForArguments: true, + parameters: [ + {argumentType: FunctionArgumentType.RANGE}, + {argumentType: FunctionArgumentType.BOOLEAN, defaultValue: false, emptyAsDefault: true}, + {argumentType: FunctionArgumentType.BOOLEAN, defaultValue: false, emptyAsDefault: true}, + ], + vectorizationForbidden: true, + }, + } + + /** + * Corresponds to UNIQUE(array, [by_col], [exactly_once]). + * + * Errors found anywhere in the input range are propagated. An empty result + * (only reachable via exactly_once when nothing occurs exactly once) yields + * #N/A, mirroring FILTER's empty-result handling. + * + * @param {ProcedureAst} ast - the parsed function-call AST node. + * @param {InterpreterState} state - current interpreter evaluation state. + */ + public unique(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('UNIQUE'), + (range: SimpleRangeValue, byCol: boolean, exactlyOnce: boolean) => { + const data = range.data + + const firstError = UniquePlugin.findFirstError(data) + if (firstError !== undefined) { + return firstError + } + + // Work in "vectors": rows for the default, columns when by_col is TRUE. + const vectors: InternalScalarValue[][] = byCol + ? UniquePlugin.transpose(data) + : data.map(row => row.slice()) + + const equalVectors = (v1: InternalScalarValue[], v2: InternalScalarValue[]): boolean => { + // v1 and v2 are always the same length here: they are rows (or columns, + // after transpose) of the same rectangular range, so no length check is + // needed before the element-wise comparison. + for (let i = 0; i < v1.length; i++) { + if (!this.arithmeticHelper.eq(v1[i] as InternalNoErrorScalarValue, v2[i] as InternalNoErrorScalarValue)) { + return false + } + } + return true + } + + // Preserve first-occurrence order; count occurrences for exactly_once. + // Deduplication is O(n^2) in the number of vectors: equality is locale-aware + // (via arithmeticHelper.eq) and not trivially hashable, so each vector is + // compared against the distinct ones found so far. This matches Excel's + // observable behavior; for very large inputs it is the known cost. + const distinct: InternalScalarValue[][] = [] + const counts: number[] = [] + for (const vector of vectors) { + const existing = distinct.findIndex(d => equalVectors(d, vector)) + if (existing === -1) { + distinct.push(vector) + counts.push(1) + } else { + counts[existing] += 1 + } + } + + const kept = exactlyOnce + ? distinct.filter((_, i) => counts[i] === 1) + : distinct + + if (kept.length === 0) { + return new CellError(ErrorType.NA, ErrorMessage.EmptyRange) + } + + const result = byCol ? UniquePlugin.transpose(kept) : kept + return SimpleRangeValue.onlyValues(result) + } + ) + } + + /** + * Predicts the output array size for UNIQUE at parse time. + * The size is data-dependent, so we predict the input size as an upper bound + * (mirroring FILTER) and return the smaller actual result at runtime. A fresh + * ArraySize is returned so the input's `isRef` flag is not propagated (an + * ArraySize flagged as a ref is treated as scalar, which would collapse the + * spilled result into a single cell). + * + * @param {ProcedureAst} ast - the parsed function-call AST node. + * @param {InterpreterState} state - current interpreter evaluation state. + */ + public uniqueArraySize(ast: ProcedureAst, state: InterpreterState): ArraySize { + if (ast.args.length < 1 || ast.args.length > 3) { + return ArraySize.error() + } + const metadata = this.metadata('UNIQUE') + const subChecks = ast.args.map((arg) => + this.arraySizeForAst(arg, new InterpreterState(state.formulaAddress, state.arraysFlag || (metadata?.enableArrayArithmeticForArguments ?? false)))) + return new ArraySize(subChecks[0].width, subChecks[0].height) + } + + /** Returns the first {@link CellError} found in a 2-D array, or undefined. */ + private static findFirstError(data: InternalScalarValue[][]): CellError | undefined { + for (const row of data) { + for (const cell of row) { + if (cell instanceof CellError) { + return cell + } + } + } + return undefined + } + + /** Transposes a 2-D array (rows <-> columns). */ + private static transpose(data: InternalScalarValue[][]): InternalScalarValue[][] { + if (data.length === 0) { + return [] + } + const height = data.length + const width = data[0].length + const result: InternalScalarValue[][] = [] + for (let c = 0; c < width; c++) { + const col: InternalScalarValue[] = [] + for (let r = 0; r < height; r++) { + col.push(data[r][c]) + } + result.push(col) + } + return result + } +} diff --git a/src/interpreter/plugin/index.ts b/src/interpreter/plugin/index.ts index a390ad843..2a3005375 100644 --- a/src/interpreter/plugin/index.ts +++ b/src/interpreter/plugin/index.ts @@ -36,6 +36,7 @@ export {RadiansPlugin} from './RadiansPlugin' export {RadixConversionPlugin} from './RadixConversionPlugin' export {RandomPlugin} from './RandomPlugin' export {SequencePlugin} from './SequencePlugin' +export {UniquePlugin} from './UniquePlugin' export {SortPlugin} from './SortPlugin' export {RoundingPlugin} from './RoundingPlugin' export {SqrtPlugin} from './SqrtPlugin' From 3b0ebc5b226baaaa42750edc148c5e7d96285b88 Mon Sep 17 00:00:00 2001 From: marcin-kordas-hoc Date: Mon, 27 Jul 2026 17:01:54 +0200 Subject: [PATCH 23/28] Agent-friendly documentation: .md companions, llms.txt, coding-agent guide (HF-154) (#1703) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Context HF-154 — make the HyperFormula docs friendlier to coding agents and LLMs. On top of the existing VuePress portal this adds: - **Per-page `.md` companions** — every doc page is also served as clean, VuePress-stripped Markdown (a build-time `md-companions` plugin), plus an aggregate `llms-full.txt`. Both are also mirrored to the site root so `/llms.txt` and `/llms-full.txt` resolve on GitHub Pages (prod) and the Netlify preview. - **`llms.txt`** — a top-level index pointing agents at the Markdown sources. - **View-as-Markdown link** (`ViewMarkdownLink.vue`) — links to the page's `.md` source so an agent can open and read it directly. - **Coding-agent setup guide** (`docs/guide/setup-coding-agent.md`) plus a `CodingAgentWizard.vue` helper. - **`context7.json`** so agent doc-access tooling (Context7 / GitMCP) can discover the sources. The Markdown stripper (`md-companions/strip.js`) is the fidelity-critical piece: it turns VuePress-flavoured Markdown (`:::` containers, ` + + diff --git a/docs/.vuepress/components/ViewMarkdownLink.vue b/docs/.vuepress/components/ViewMarkdownLink.vue new file mode 100644 index 000000000..287f4dfa7 --- /dev/null +++ b/docs/.vuepress/components/ViewMarkdownLink.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/docs/.vuepress/components/clipboard.js b/docs/.vuepress/components/clipboard.js new file mode 100644 index 000000000..694b8b1bc --- /dev/null +++ b/docs/.vuepress/components/clipboard.js @@ -0,0 +1,29 @@ +/** + * Copy text to the clipboard, falling back to a hidden `