fix(format): support thousands grouping and format sections in numberFormat/TEXT (HF-287) - #1716
fix(format): support thousands grouping and format sections in numberFormat/TEXT (HF-287)#1716marcin-kordas-hoc wants to merge 11 commits into
Conversation
✅ Deploy Preview for hyperformula-dev-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
bugbot run |
98bb963 to
8a64987
Compare
Performance comparison of head (975b72c) vs base (114fd5d) |
8a64987 to
b92d6c0
Compare
…Format/TEXT (HF-287)
The TEXT number formatter understood only a single simple mask
(`[#0]+(\.[#0]*)?`), so complex masks leaked their unparsed tail into the
output (e.g. `TEXT(1234.5,"#,##0.00")` -> `1235,##0.00`) and it ignored
the instance's configured separators.
Extend the existing formatter in place (Option A):
- parser.ts: widen the number-format regex to a FLAT class `[#0,]+(\.[#0]*)?`
that admits the grouping comma (no nested quantifier — DEV-2120 ReDoS
discipline). Export its source for a white-box shape test.
- format.ts: strip presentational color tags (`[Red]`, ...) after the currency
callback and before date/time dispatch; split the mask into sign-selected
sections (positive;negative;zero) honoring quotes/escapes; thread Config so
the decimal glyph uses `decimalSeparator` and grouping uses `thousandSeparator`
(empty on default config -> no visible glyph). Sign is extracted on `abs`,
fixing the pre-existing `padLeft('-5',3)` bug (`TEXT(-5,"000.00")` -> `-005.00`).
Trailing scaler commas degrade to a visible literal rather than silently
mis-scaling. Parse failures fall back to the cleaned format string.
No public API change, no i18n, no grammar rewrite. Percent scaling, scaler
arithmetic, `?` placeholders, scientific notation and `[condition]` comparators
remain out of scope.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b92d6c0 to
fa890f6
Compare
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit fa890f6. Configure here.
The public test/ directory holds smoke tests only; internal test suites live in the hyperformula-tests repository. The spec moves there unchanged in coverage, split into single-assertion cases: handsontable/hyperformula-tests#27. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cks (HF-287) Color tags are removed from the format string before it reaches the stringifyDateTime and stringifyDuration callbacks, so a custom callback no longer sees them. That is client-visible and belongs in the entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The file cites the reporting issue in 93 entries and a PR in 12, and HF-24's stringifyCurrency entry in this same release already points at #1145. That issue asked for two things — support for the $#,##0.00 format, or a stringifyCurrency config option. HF-24 shipped the alternative; this change delivers the primary ask, so the entry references the issue rather than the pull request. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
hyperformula-docs | 975b72c | Commit Preview URL Branch Preview URL |
Aug 31 2026, 05:20 AM |
This morning's develop merge landed the TEXT/numberFormat entry inside the already-published 3.4.0 Fixed list, putting CHANGELOG.md out of sync with docs/guide/release-notes.md for that released version. The fix is unreleased, so the entry belongs under [Unreleased]; the 3.4.0 lists in the two files match again. Same correction as 63e2137 on #1714. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…HF-287)
Two divergences measured against real Excel via the MS Graph workbook
API (2026-08-28):
- TEXT(-0.4,"0") rendered "-0" — the implicitly-prepended minus is now
dropped when every rendered digit rounds to zero; explicit `-` mask
literals and raw-sign section selection are unchanged.
- TEXT(1E+21,"#,##0") rendered JS scientific notation ("1e+21") — doubles
>= 1e21 are always integer-valued, so they are expanded to their exact
digits via BigInt before padding and grouping.
tsconfig lib gains es2020.bigint for the BigInt global — a types-only
change with no runtime or output impact (all supported browsers and Node
versions provide BigInt).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 975b72c. Configure here.
|
|
||
| - Fixed the MAXPOOL and MEDIANPOOL functions throwing an uncaught `TypeError` instead of returning the `#VALUE!` error when the range dimensions are not a whole multiple of the window size and the stride. [#1718](https://github.com/handsontable/hyperformula/pull/1718) | ||
| - Fixed the `MOD` function returning a remainder with the sign of the dividend instead of the sign of the divisor, which made the results differ from Excel and Google Sheets for arguments with opposite signs (e.g. `=MOD(-3, 12)` now returns `9` instead of `-3`). [#1747](https://github.com/handsontable/hyperformula/issues/1747) | ||
| - Fixed the `TEXT` function so that number-format masks with thousands grouping (`#,##0`) and positive/negative/zero sections (`0.00;(0.00)`) are formatted correctly instead of leaking the unparsed mask into the output. The built-in number formatter now also honors the configured `decimalSeparator` and `thousandSeparator` and ignores color tags such as `[Red]`, which are now removed from the format string before it reaches the `stringifyDateTime` and `stringifyDuration` callbacks. Matching Excel, a negative value whose rendered digits all round to zero is now displayed without the minus sign (`TEXT(-0.4, "0")` returns `0`), and magnitudes of `1e21` and above are expanded to their full digits instead of scientific notation. [#1145](https://github.com/handsontable/hyperformula/issues/1145) |
There was a problem hiding this comment.
CHANGELOG uses issue not PR link
Low Severity
The new Unreleased TEXT/numberFormat bullet ends with an issue URL for #1145 rather than a GitHub pull-request link. Unreleased changelog items need a pull/NNNN link; an issue-only reference does not count.
Triggered by learned rule: CHANGELOG bullets need a PR link
Reviewed by Cursor Bugbot for commit 975b72c. Configure here.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1716 +/- ##
===========================================
+ Coverage 97.32% 97.33% +0.01%
===========================================
Files 195 195
Lines 15739 15810 +71
Branches 3390 3402 +12
===========================================
+ Hits 15318 15389 +71
Misses 421 421
🚀 New features to boost your workflow:
|
Tobiadefami
left a comment
There was a problem hiding this comment.
Looks good. Grouping respects the configured separators, and positive, negative, and zero values select the expected format sections.


What & why
TEXT/numberFormatonly understood a single simple mask ([#0]+(\.[#0]*)?); complex masks emitted garbage — e.g.TEXT(1234.5,"#,##0.00")→"1235,##0.00". It ignored thousands grouping (#,##0), format sections (;), and HF's configureddecimalSeparator/thousandSeparator.How (Option A — extend the formatter in place; no grammar rewrite, no public API, no i18n)
src/format/parser.ts: widennumberFormatRegexto admit,into the flat character class ([#0,]+(\.[#0]*)?) — no nested quantifier (keeps the DEV-2120 ReDoS discipline); white-box shape test via exportedNUMBER_FORMAT_REGEX_SOURCE.src/format/format.ts: section split (positive;negative;zero, quote/escape-aware), grouping withconfig.thousandSeparator, decimal viaconfig.decimalSeparator, sign onabs, color-tag strip applied AFTER thestringifyCurrencycallback (doesn't disturb that extension point), parse-failure falls back to the cleaned format string.compatibility-with-microsoft-excel.md) reworded as nuances; CHANGELOG under Fixed.Contract note —
stringifyDateTime/stringifyDurationThe color-tag strip sits between the
stringifyCurrencycallback and date/time dispatch, so those two callbacks now receive the format string without color tags: a customstringifyDateTimeseesdd-mm-yyyy, not[Red]dd-mm-yyyy.stringifyCurrencystill receives the raw string.That placement is what fixes
[Red]dd-mm-yyyy: thedinRedused to be read as a day token, so the mask rendered[Re1]01-01-1900. Stripping only inside the number path would leave that bug in place. Noted in the CHANGELOG entry.Verified against Excel (HF-287 repro table)
The
$#,##0.00case was originally reported in #1138 (=TEXT(1234.567,"$#,##0.00")→$1235,##0.00instead of$1,234.57), which was closed in favour of #1145. HF-24 shipped that issue's alternative — thestringifyCurrencycallback; this PR delivers its primary ask, so the CHANGELOG entry points at #1145.#,##0.001,234.50,)#,##0.00;-#,##0.00-1,234.50#,##0.00 "zł"1 234.50 zł)$#,##0.00;-$#,##0.00-$1,234.50000.00-005.00Tests — handsontable/hyperformula-tests#27
All HF-287 coverage lives in the private suite; this PR adds no test files.
unit/format/format-number-sections.spec.ts— 58 single-assertion cases: grouping against a configuredthousandSeparator, sign-selected sections, config decimal separator, color-tag stripping, literal-only sections, backslash escapes, degradation, regression locks on the simple masks, engine-levelTEXT, and the white-box ReDoS shape assertion.unit/interpreter/function-text.spec.ts— one existing assertion flips (correct-behavior change):TEXT(12.45,"$###,##0.00")was'$12,##0.00'(garbage), now'$12.45'. Without the paired update, unit-tests CI fails on the old expectation.Local run against this head: 8 suites / 233 tests green across the new spec plus
unit/format/,function-text,function-textjoin,config,update-configand smoke;tsc -p tsconfig.test.jsonclean; eslint 0 errors on the touched files.A before/after sweep over 45 masks (base
d860eef3evs this head) shows changes only where the old output was garbage. Two cases worth naming:TEXT(5,"0,000")was5,000and is now0005on default config (the old output only looked grouped — it was the mask tail leaking), and[Red]dd-mm-yyyywas[Re1]01-01-1900.Scoped OUT (per ADR — not regressions)
percent
0.00%, scaler arithmetic (degrades visibly),?placeholders, scientificE+,@/4th text section,[condition]comparators, and placeholder chars inside quoted literals. Also:=TEXT(x,"… ""zł""")as a literal formula still returns#ERROR!— HF's formula parser rejects embedded""(unrelated to the formatter, which does handle the quoted literal when the format arrives via config/programmatically).Note
Medium Risk
Changes core
TEXTformatting behavior across many masks and alters the format string passed to custom date/time callbacks; incorrect edge-case handling could affect displayed cell values but is localized to the format layer.Overview
Fixes
TEXTnumber masks that previously leaked unparsed format fragments (e.g.#,##0.00showing,##0in the output).The built-in number path now splits Excel-style positive/negative/zero sections (
;), applies thousands grouping from#,##0usingthousandSeparator/decimalSeparator, strips[Red]-style color tags before date/time dispatch (so[Red]dd-mm-yyyyno longer mis-parses), and aligns with Excel on signed zeros (TEXT(-0.4,"0")→0) and very large magnitudes (≥1e21expanded viaBigInt). The number tokenizer admits grouping commas with a ReDoS-safe flat regex and skips placeholder-less runs as literals.Docs/CHANGELOG document built-in coverage limits (no
%, scientific, conditions) and the note that customstringifyDateTime/stringifyDurationcallbacks receive color-stripped format strings whilestringifyCurrencystill gets the raw mask.Reviewed by Cursor Bugbot for commit 975b72c. Bugbot is set up for automated code reviews on this repo. Configure here.