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 7 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 (51ed68a) vs base (ebaa2b2) |
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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0e91ba4. Configure here.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
hyperformula-docs | 51ed68a | Aug 07 2026, 05:09 PM |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1716 +/- ##
===========================================
- Coverage 97.31% 96.74% -0.58%
===========================================
Files 195 195
Lines 15734 15799 +65
Branches 3456 3467 +11
===========================================
- Hits 15312 15285 -27
- Misses 414 500 +86
- Partials 8 14 +6
🚀 New features to boost your workflow:
|

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 default TEXT output for many number masks and alters format strings passed to custom date/time callbacks after color stripping; scope is localized to formatting but affects widely used TEXT behavior.
Overview
Fixes
TEXT/ built-in number formatting so masks like#,##0.00and multi-section patterns (0.00;(0.00)) render real grouped numbers instead of leaking unparsed mask text into the result.The number path now splits on
;(quote/escape-aware), picks positive/negative/zero sections, appliesconfig.thousandSeparatorandconfig.decimalSeparator, and renders placeholder-less sections as literals.stripColorTagsruns afterstringifyCurrencybut before date/time dispatch so[Red]no longer breaks date masks andstringifyDateTime/stringifyDurationreceive color-stripped format strings.parser.tswidens the flat number regex to admit grouping commas while keeping a ReDoS-safe shape.Docs and CHANGELOG document built-in number support limits (no
%, scientific, conditions, etc.) and config-driven separators.Reviewed by Cursor Bugbot for commit 51ed68a. Bugbot is set up for automated code reviews on this repo. Configure here.