refactor(metadata): use snake_case for all catalogue parameter names (HF-249) - #1709
Merged
sequba merged 1 commit intoJul 14, 2026
Conversation
…(HF-249) Normalize every FunctionDoc parameter name to snake_case for a consistent function-picker UI, replacing the mixed Title-case/underscore styles (e.g. Sum_Range/Sumrange/Number1/Number_1). Sibling params now agree (SUMIF & SUMIFS both use sum_range; MATCH & VLOOKUP both search_criterion). The migration generator (scripts/hf249-migrate-function-docs.ts) now emits snake_case via a shared toSnakeCase helper (camel/acronym splitting, separator unification, digits kept attached) plus a small segmentation table for run-on tokens the docs left unsplit; its main() is guarded so the helper is importable without side effects. Descriptions, examples and documentationUrl are untouched; custom-function positional names (Arg1, Arg2) are unchanged. Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
Contributor
|
Task linked: HF-249 HF function documentation available via API |
Performance comparison of head (07161a0) vs base (18d4246) |
sequba
marked this pull request as ready for review
July 14, 2026 13:36
sequba
merged commit Jul 14, 2026
4612e75
into
feature/hf-249-function-metadata-api
28 checks passed
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## feature/hf-249-function-metadata-api #1709 +/- ##
========================================================================
+ Coverage 96.42% 96.61% +0.19%
========================================================================
Files 192 192
Lines 15585 15585
Branches 3447 3447
========================================================================
+ Hits 15028 15058 +30
+ Misses 557 527 -30
🚀 New features to boost your workflow:
|
marcin-kordas-hoc
added a commit
that referenced
this pull request
Jul 14, 2026
…(HF-300) Post-merge with the updated base (snake_case params #1709 + new functions): - Author XIRR, VSTACK, HSTACK, and fill TBILLEQ/TBILLPRICE 'discount' (base-added). - Restore examples mis-attached during conflict resolution: SEQUENCE, XNPV, GCD. - Remove SUM's stale per-function documentationUrl re-introduced by the merge (the shared .html default supplies it; comment already said it's absent). Source: https://app.clickup.com/t/86caprtgj ADR: adr/2026-07-13-hf300-function-metadata-enrichment.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
sequba
added a commit
that referenced
this pull request
Jul 31, 2026
…-249) (#1692) ## What & why A function picker — the Formula Builder's, or any integrator's — needs to answer two questions: *what functions exist?* and *what do this function's arguments mean?* HyperFormula could only answer the first. `getRegisteredFunctionNames()` returned 423 translated names and nothing else; `getAllFunctionPlugins()` exposed `implementedFunctions`, which has coercion rules and arity but no category, description, human-readable parameter names or examples. Everything else a picker needs existed only as prose, in the 551-line hand-maintained `docs/guide/built-in-functions.md`. And prose with no second copy drifts silently. That page published `FVSCHEDULE` as `FV(Pv, Schedule)`, `RANDBETWEEN` as `RAND(...)`, `COLUMN` as `COLUMNS(...)`, `F.TEST` with `Z.TEST`'s signature, `T.TEST` with two of its four arguments, `DAYS360`'s arguments reversed; it listed `NORMDIST` twice (the second row was really `NORMSDIST`) and omitted `VERSION`, callable and counted in the page's own printed total since 2020. Every one is a copy-paste from a neighbouring row — the signature of data nothing can check against the thing it describes. **So this PR moves that data into `src/` and reads it back through the engine's own public API, making the published reference just its first consumer.** A new authored catalogue under `src/interpreter/functionMetadata/` (370 entries, one file per category, 730 parameter descriptions and 679 examples) supplies category, description, `snake_case` parameter names and descriptions, examples and a docs link; `implementedFunctions` still supplies arity and optionality. Two methods expose the join, static and per-instance: - **`getAvailableFunctions(code)`** → one cheap entry per function (`localizedName`, `canonicalName`, `category`, `shortDescription`, plus `aliasOf` on the 53 aliases), sorted by localized name under a collator built from that language — enough to paint all 423 picker rows without a second call. - **`getFunctionDetails(canonicalName, code)`** → adds the ordered `parameters` (name, description, `optional`), `repeatLastArgs`, `documentationUrl` and `examples`. Deliberately **no pre-rendered syntax string**; the caller composes `SUMIF(range, criteria, [sum_range])` itself, and `script/formatFunctionSyntax.ts` is a reference implementation of that renderer, kept out of `src/` on purpose. `docs/guide/built-in-functions.md` becomes a build product of *the public API* rather than of the catalogue directly — so generating the page exercises the same alias resolution, listability gate and optionality derivation a customer's picker will. It leaves git, is gitignored, and is regenerated from `built-in-functions.tmpl.md` as the first step of `docs:dev`/`docs:build`. "The docs are wrong" and "the API is wrong" are now the same bug. The regenerated page adds `VERSION`, gives `NORMSDIST` its own row, adds a table of contents and a per-function anchor, and corrects optionality on **27 functions** the old page showed as required (`IF`, `LOG`, `ROUND`, `SUMIF`, `VLOOKUP`, the `*2*` conversions, …). Nine more change only *notation*: a repeating argument group is now rendered as `...` against `repeatLastArgs` instead of the old hand-written `[Range2, Criterion2 [, ...RangeN, CriterionN]]`. `SWITCH` was also semantically wrong — its parameters were `expression, value1, expression2`, but the third argument is the *result* returned on a match. ## Design decisions worth a second opinion - **The catalogue is authored data, not derived**, so it must be kept in step with `implementedFunctions` by hand. Parameter *count* is cross-checked, and on a mismatch **the implementation wins**: `getFunctionDetails` reports one parameter per implemented argument under positional names (`Arg1`, `Arg2`, …), discards the authored names and descriptions, and warns on the console naming the function. Category, description, examples and URL still come from the entry, and the function stays listed in both tiers — drift costs the parameter prose, never the availability. `DEV_DOCS.md` documents this and the remaining silent-failure mode (an entry left behind after a rename describes nothing and merely ships in the bundle). - **Optionality is deliberately not authored or cross-checked** — `optional` comes only from `optionalArg`/`defaultValue`. Hence the single production edit outside the new module: `optionalArg: true` on `SHEET`/`SHEETS`, which have always accepted `=SHEET()` while declaring the argument required. Metadata-only and behaviour-neutral (`runFunctionWithReferenceArgument` returns before argument-count validation), and a sweep of all 423 ids found no other function with this mismatch. - **One rule decides how a function is described: does the catalogue hold an entry for its id?** The catalogue is keyed by id, not by implementation, so a user plugin registered over `SUMIF` is described with `SUMIF`'s authored category and description, over its own signature. An earlier revision gated this on a snapshot of built-in plugin ownership, so a shadow reported as `'Custom'`; that is gone. It bought little — a plugin re-implementing `SUMIF` is usually still a `SUMIF` — and cost a module-init hook in `index.ts` (the plugin identities can only come from the plugin barrel, and importing it from the registry creates a load-order cycle that breaks the bundled build) plus a second way for the whole built-in set to silently degrade to `'Custom'` if that hook ever failed to run. **This is the bullet I'd most like a second opinion on.** - **Both tiers describe every registered function, custom ones included.** `registerFunctionPlugin` is global, so a custom function is callable everywhere and the static methods list it; the instance methods list that instance's own registry, which differs when it was built with the `functionPlugins` option. An id with no translation entry for the active language is omitted from both, because the interpreter refuses to evaluate it. - **A custom function omits the fields it cannot author**, rather than reporting an empty one: `shortDescription`, `documentationUrl` and `examples` are absent (and optional in the public types, as `aliasOf` already was), so a consumer can tell "no authored description" from "an empty one" and the object survives `JSON.stringify` unchanged. Built-ins are unaffected — the catalogue authors all three for every entry. - **An instance describes its functions under the translation package it was built with**, not under whatever is registered globally for that code today. Otherwise re-registering a language could make the API advertise a localized name that instance refuses to evaluate. - **Exported:** `FunctionListEntry`, `FunctionDetails`, `FunctionParameterDescription`, `FunctionCategory`. `FUNCTION_CATEGORIES`, `FunctionDoc` and `CUSTOM_FUNCTION_CATEGORY` stay internal — so a TS consumer cannot enumerate the categories to build a filter and must compare against `'Custom'` as a string. Worth confirming that is the right line. - **`canonicalName` is matched exactly**: case-sensitive (`'sumif'` → `undefined`, though `=sumif(...)` evaluates) and canonical English only. Likeliest integration pitfall for a picker holding translated names. ## Already reviewed Roughly three-quarters of the develop-diff is already reviewed and merged, as sub-PRs into this branch: **#1699** (page generated from the API), **#1705** (HF-300: examples, docs URLs, parameter descriptions), **#1709** (`snake_case` parameter names), **#1710** (invalid-locale collator guard). New here: the metadata API itself, the catalogue-keyed resolution rule, custom functions in the static tier, `SHEET`/`SHEETS`, the generated table of contents, and ~55 descriptions rewritten because they documented Excel rather than HyperFormula — with the deviations added to `list-of-differences.md` (`INT` truncates toward zero, `MOD` takes the dividend's sign, `ISEVEN`/`ISODD` don't truncate, `CEILING.MATH`/`FLOOR.MATH` honour only `mode` = 1). ## How I tested Paired suite: [handsontable/hyperformula-tests#14](handsontable/hyperformula-tests#14) (branch `feature/hf-249-function-metadata-api`), 134 tests for this API alone, green with the full repository suite (502 files, 6,214 tests). It covers the static/instance split, i18n across all 18 packs, aliases, custom functions, plugins shadowing a built-in id or a built-in alias id, locale-aware ordering, and prototype-key ids (`toString`, `__proto__`) — plus the two invariants most worth protecting: **every canonical id declared by a registered built-in plugin resolves to details**, so a missing catalogue entry fails CI instead of silently dropping a function, and **the list and the details always agree on which ids exist**. Each guard was mutation-tested: broken deliberately, confirmed red, reverted. Assertions avoid jest-only matchers and never rely on jest ignoring a key valued `undefined`, so they fail under the jasmine/karma browser job too. Separately, all 679 authored examples parse and name their own function, and a sampled slice is pinned to Excel-cross-checked values. ## Known trade-offs - **`examples` are English-spelled and `OFFSET` is lexed from its translated name**, so `getFunctionDetails('OFFSET','deDE').examples` yields `#NAME?` in all 16 non-English packs — and `ISREF`'s example embeds `OFFSET`, returning `true` in enGB but `false` in plPL with no error. The one item I'd want accepted with eyes open. - **`SWITCH` publishes `repeatLastArgs: 1`**, understating its (value, result) pair group. It cannot simply become `2`: the field also drives runtime arity validation, and the optional trailing default needs a step of 1. - **`documentationUrl` is the same page for all 423 ids.** Per-function anchors now exist on the generated page, so `#${canonicalName}` is a follow-up, not a redesign. - **The catalogue ships in the bundle** (~25 KB gzipped) and is not tree-shakeable — `HyperFormula` and `FunctionRegistry` both import it eagerly. ## Open for the reviewer - **`CHANGELOG.md`** names only the two methods; it should also name the four exported types, and needs a `### Changed` line for `SHEET`/`SHEETS` now reporting their argument as optional. - **`DEV_DOCS.md` carries general engineering policy** unrelated to HF-249 (a `## Performance` section, six code-style bullets, and additions to Definition of Done, Automatic tests and Documentation) — which the atomic-PR rule added in this same PR says belongs elsewhere. Split them out, or accept them explicitly. Source: https://app.clickup.com/t/9015210959/HF-249 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Introduces a large, permanent public API and ships the full catalogue in the bundle (~25 KB gzipped), while changing how published function reference docs are produced; runtime formula evaluation is largely unchanged aside from metadata alignment (e.g. optional reporting for zero-arg reference functions). > > **Overview** > Adds **`getAvailableFunctions`** and **`getFunctionDetails`** (static and instance) so integrators can build function pickers from engine data instead of scraping docs. Metadata is authored in a new per-category catalogue under `src/interpreter/functionMetadata/` (joined with `implementedFunctions` for arity, optionality, and `repeatLastArgs`); custom functions appear with category `'Custom'` and positional `ArgN` names unless a plugin shadows a built-in id, in which case the catalogue entry for that id still applies. > > The hand-maintained **`docs/guide/built-in-functions.md`** is removed from version control and **regenerated** at build time from `built-in-functions.tmpl.md` plus the same API (`npm run docs:generate-function-docs`, wired into `docs:dev` / `docs:build`). VuePress excludes the template from routes and disables “edit this page” on the generated guide. > > Also exports **`FunctionListEntry`**, **`FunctionDetails`**, **`FunctionParameterDescription`**, and **`FunctionCategory`**; documents the catalogue workflow in **`DEV_DOCS.md`**; expands **`repeatLastArgs`** guidance in the custom-functions guide; and records additional Excel vs HyperFormula differences in **`list-of-differences.md`**. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f22560e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Kuba Sekowski <jakub.sekowski@handsontable.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com> Co-authored-by: Kuba Sekowski <kuba.sekowski.dev@gmail.com> Co-authored-by: Cursor Opus 5 <noreply@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Addresses a code-review finding on #1692: the function-metadata catalogue mixed several parameter-name conventions, which surface directly in a function-picker UI. Even closely-related functions disagreed —
SUMIFusedRange, Criteria, Sumrange,SUMIFSusedSum_Range, Criterion_range1, Criterion1,AVERAGEIFusedRange, Criterion, Average_Range— mixing Title-case,snake_Case, and run-on tokens.This normalizes every
FunctionDocparameter name tosnake_caseso the catalogue is internally consistent.What changed
categories/*.tsfiles are nowsnake_case(lowercase words joined by_, camelCase/acronym boundaries split, separators unified, trailing digits kept attached:Number1/Number_1→number1).Sumrange→sum_range(matchesSUMIFS),Searchcriterion→search_criterion(matchesVLOOKUP/HLOOKUP), pluslogical_value,date_string,time_string,start_date,minimum_length,lower_bound,upper_bound,number_x,number_y.scripts/hf249-migrate-function-docs.ts) now emitssnake_casevia a shared, exportedtoSnakeCasehelper (+ a small segmentation table), so a future regeneration stays consistent. Itsmain()is guarded withrequire.main === moduleso the helper can be imported without side effects.descriptions,examples,documentationUrl, categories, short descriptions, ordering. Custom-function positional names (Arg1,Arg2, …) are unchanged — they are a documented, self-consistent placeholder for user functions that ship no metadata (see open question below).How did you test your changes?
tsc --noEmit— clean.eslinton the changed files — 0 errors.test/smoke.spec.ts— 4/4 pass.getFunctionDetailsparameter name matches^[a-z][a-z0-9]*(_[a-z0-9]+)*$;getAvailableFunctions/getFunctionDetailslist↔details parity still holds;SUMIF&SUMIFSboth exposesum_range;MATCH&VLOOKUPboth exposesearch_criterion;Arg1,Arg2.HSTACK/XIRR, the hand-authoredSUM/SUMIFfields, and a staleTEXTdescription — so the regen output was discarded).Types of changes
Related issues:
Notes for reviewers
hyperformula-tests#14) asserts specific parameter names (e.g.SUMIFparams); those expectations need updating to the newsnake_casenames.Arg1/Arg2. If you'd also like those lowercased (arg1/arg2), it's a 1-line change inbuildCustomFunctionDetailsplus acustom-functions.mdwording update — say the word.sumrange→sum_range, etc.) are the only editorial decisions here; everything else is mechanical.