Skip to content

feat(functions): implement VSTACK and HSTACK (HF-71)#1698

Merged
sequba merged 5 commits into
developfrom
feature/hf-71-vstack-hstack
Jul 14, 2026
Merged

feat(functions): implement VSTACK and HSTACK (HF-71)#1698
sequba merged 5 commits into
developfrom
feature/hf-71-vstack-hstack

Conversation

@marcin-kordas-hoc

@marcin-kordas-hoc marcin-kordas-hoc commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

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.tsVSTACK/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.mdAdded 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


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 for commit 3db462b. Bugbot is set up for automated code reviews on this repo. Configure here.

Add the VSTACK and HSTACK array-manipulation functions to ArrayPlugin,
mirroring the existing FILTER implementation pattern.

- VSTACK stacks input arrays vertically: result height = sum of input
  heights, width = max input width. Narrower rows are padded on the
  right with #N/A.
- HSTACK stacks input arrays horizontally: result width = sum of input
  widths, height = max input height. Shorter columns are padded at the
  bottom with #N/A.
- Both are variadic (repeatLastArgs: 1) and accept ranges, array
  literals and scalars (FunctionArgumentType.RANGE,
  enableArrayArithmeticForArguments).
- Result dimensions are declared at parse time via the
  sizeOfResultArrayMethod (vstackArraySize / hstackArraySize) so the
  result spills correctly, consistent with HyperFormula's parse-time
  array sizing. Padding uses ErrorType.NA (HyperFormula has no #CALC!).
- Add translations for all 17 built-in language packs (English name in
  every locale, matching Excel/Sheets, which do not localize these
  functions). enUS inherits enGB.
- Document both functions in the built-in functions guide and add a
  changelog entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@netlify

netlify Bot commented Jun 23, 2026

Copy link
Copy Markdown

Deploy Preview for hyperformula-dev-docs ready!

Name Link
🔨 Latest commit 3db462b
🔍 Latest deploy log https://app.netlify.com/projects/hyperformula-dev-docs/deploys/6a560b85b75664000848766f
😎 Deploy Preview https://deploy-preview-1698--hyperformula-dev-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@qunabu

qunabu commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Task linked: HF-71 Implement VSTACK and HSTACK functions

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4abdae5. Configure here.

Comment thread src/interpreter/plugin/ArrayPlugin.ts
@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown

Performance comparison of head (3db462b) vs base (92e6c73)

                                     testName |   base |   head |  change
-------------------------------------------------------------------------
                                      Sheet A | 507.27 | 501.89 |  -1.06%
                                      Sheet B | 164.57 | 171.58 |  +4.26%
                                      Sheet T | 143.36 | 148.39 |  +3.51%
                                Column ranges | 534.61 | 542.11 |  +1.40%
Sheet A:  change value, add/remove row/column |  18.04 |   20.5 | +13.64%
 Sheet B: change value, add/remove row/column | 152.29 |  161.8 |  +6.24%
                   Column ranges - add column | 167.98 | 177.06 |  +5.41%
                Column ranges - without batch | 507.43 | 526.65 |  +3.79%
                        Column ranges - batch | 128.76 | 135.52 |  +5.25%

marcin-kordas-hoc and others added 3 commits June 25, 2026 15:03
…(HF-71)

HSTACK indexed sourceRow[col] directly, trusting every row to be as wide as
the range. A jagged SimpleRangeValue (a row shorter than range.width(),
reachable via a custom function) produced an empty cell where VSTACK
produces #N/A. Reuse padRowToWidth so both functions pad identically.

Reported by Cursor Bugbot on PR #1698.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The entry linked #1690 ("Fix docs AGENTS standards link"), an unrelated PR,
via /issues/. Correct it to this feature's PR #1698.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-71)

- Add JSDoc to the new vstackArraySize/hstackArraySize/stackSubChecks/
  padRowToWidth methods (sequba: JSDoc required on new public surface).
- HSTACK pads rows inline with an index guard instead of building an
  intermediate padded array via padRowToWidth, avoiding a per-row slice +
  spread allocation in the common rectangular path. Behaviour is unchanged
  (covered by the jagged-input and empty-array regression tests); VSTACK
  keeps padRowToWidth, whose copy it relies on per result row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@marcin-kordas-hoc marcin-kordas-hoc requested a review from sequba July 1, 2026 21:58
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.70%. Comparing base (92e6c73) to head (3db462b).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1698      +/-   ##
===========================================
- Coverage    97.15%   96.70%   -0.46%     
===========================================
  Files          176      176              
  Lines        15404    15450      +46     
  Branches      3409     3417       +8     
===========================================
- Hits         14966    14941      -25     
- Misses         438      509      +71     
Files with missing lines Coverage Δ
src/i18n/languages/csCZ.ts 100.00% <ø> (ø)
src/i18n/languages/daDK.ts 100.00% <ø> (ø)
src/i18n/languages/deDE.ts 100.00% <ø> (ø)
src/i18n/languages/enGB.ts 100.00% <ø> (ø)
src/i18n/languages/esES.ts 100.00% <ø> (ø)
src/i18n/languages/fiFI.ts 100.00% <ø> (ø)
src/i18n/languages/frFR.ts 100.00% <ø> (ø)
src/i18n/languages/huHU.ts 100.00% <ø> (ø)
src/i18n/languages/idID.ts 100.00% <ø> (ø)
src/i18n/languages/itIT.ts 100.00% <ø> (ø)
... and 8 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sequba sequba merged commit 3064a8b into develop Jul 14, 2026
35 of 36 checks passed
@sequba sequba deleted the feature/hf-71-vstack-hstack branch July 14, 2026 10:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants