Function metadata API: getAvailableFunctions / getFunctionDetails (HF-249)#1692
Function metadata API: getAvailableFunctions / getFunctionDetails (HF-249)#1692marcin-kordas-hoc wants to merge 36 commits into
Conversation
…c API (HF-249) Add static and instance getAvailableFunctions and getFunctionDetails to HyperFormula, backed by the function metadata catalogue. Instance methods delegate to the static ones using the configured language. Re-export the public catalogue types from the package entry point.
Replace the seeded catalogue with all 363 canonical built-ins, generated from docs/guide/built-in-functions.md by scripts/hf249-migrate-function-docs.ts (dev-only, not shipped). Parameter names come from the Syntax column but their count and optionality follow the implementation arity; repeating groups collapse and duplicate names are disambiguated. Also fall back to the canonical id when a language pack leaves a function name empty (e.g. SWITCH in several locales).
…-249) Add a CHANGELOG entry for the new built-in function metadata API, and a "Function metadata" section to the custom functions guide clarifying that the catalogue covers built-ins only — custom functions are absent from getAvailableFunctions() and getFunctionDetails() returns undefined for them.
Performance comparison of head (40b6468) vs base (e0e1261) |
✅ Deploy Preview for hyperformula-dev-docs ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
8983d26 to
3f8857b
Compare
…ith getFunctionDetails (HF-249) getAvailableFunctions derived its id set from each plugin class's static implementedFunctions, so an unregistered built-in (or a registry mutation) could still appear in the list while getFunctionDetails returned undefined for it. Gate the list on FunctionRegistry.getFunctionPlugin(id) — the same resolution getFunctionDetails uses — so list and details never disagree. (Cursor Bugbot, #1692.)
…tFunctionDetails agree (HF-249) getAvailableFunctions filtered on documented+registered but not the catalogue-vs-implementation arity check that getFunctionDetails applies, so a drifted function could appear in the list yet return undefined from details. Extract resolveListableMetadata (documented + registered + arity-consistent) and use it in both. (Cursor, #1692.)
…249) Align parameter display names in the generated function-metadata catalogue with their Title-Case siblings: - financial.ts: CUMIPMT `type` -> `Type` (matches CUMPRINC); RATE `guess` -> `Guess` - statistical.ts: BESSELI/J/K/Y `x`,`n` -> `X`,`N` Also add an instance-method example to the "Function metadata" section of docs/guide/custom-functions.md so the "available both as static and instance methods" claim is backed by a snippet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Task linked: HF-249 HF function documentation available via API |
…-249)
buildFunctionDescriptions eagerly imported the plugin barrel to detect
built-in ownership; since HyperFormula loads the metadata builders early,
that created a module-load-order cycle crashing the bundled build
('Class extends value undefined' in the perf benchmark). Move the
built-in owner snapshot to FunctionRegistry (captured in index.ts, which
already imports the barrel to register built-ins) and expose
isBuiltinFunction; the builders no longer import the barrel. Shadow-vs-
built-in behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…adata (HF-249) Per review: the metadata guide need not describe what the API does NOT return. Keep only the repeatLastArgs explanation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/examples on FunctionDoc (HF-249) Add optional documentationUrl/examples to FunctionDoc and surface them in getFunctionDetails (falling back to ''/[] when absent). Author SUM and SUMIF as reference functions with real parameter descriptions, examples and a documentation link so the Formula Builder team can test rendering of populated metadata. The migration generator does not emit these fields; a comment guards the two entries against regeneration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ails (HF-249) The custom-functions guide listed the getFunctionDetails return shape but omitted the documentationUrl and examples fields; add them so the documented shape matches FunctionDetails. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| ``` | ||
| ::: | ||
|
|
||
| ## Function metadata |
There was a problem hiding this comment.
I think it's better to describe getAvailableFunctions and getFunctionDetails functions in the built-in-functions page instead of custom-functions
There was a problem hiding this comment.
Perfect! I've successfully fixed both issues:
Changes Made
1. Added Missing Catalogue Entries (commit c65115b)
- Added
XIRRtosrc/interpreter/functionMetadata/categories/financial.ts - Added
VSTACKandHSTACKtosrc/interpreter/functionMetadata/categories/array-manipulation.ts - Moved Function metadata API documentation from
custom-functions.mdtobuilt-in-functions.md
2. Fixed Static API to Exclude Custom Plugins (commit 86ab411)
- Modified
FunctionRegistry.getListableFunctionIds()to filter out globally-registered custom plugins, returning only built-in functions and aliases - Modified
HyperFormula.getFunctionDetails()static method to returnundefinedfor custom plugins instead of partial metadata - Both changes use the existing
isBuiltinFunction()check to properly distinguish built-ins from custom plugins
Verification
✅ Linter passes (0 errors, only pre-existing warnings)
✅ Jest smoke tests pass (4/4 tests)
✅ Built-in functions like XIRR, VSTACK, HSTACK are correctly included in static API
✅ Static methods now properly exclude globally-registered custom plugins per documentation
The static metadata API now correctly returns only built-in functions and their aliases, while instance methods continue to include custom plugins as intended.
|
@cursor fix the review comment |
- Add XIRR to Financial category with Values, Dates, Guess parameters - Add VSTACK and HSTACK to Array manipulation category with Array1 parameter - Move Function metadata section from custom-functions.md to built-in-functions.md - Fixes Bugbot review: Missing catalogue mislabels builtins Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
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 c65115b. Configure here.
Static getAvailableFunctions and getFunctionDetails now filter out custom plugins registered via HyperFormula.registerFunctionPlugin, returning only built-in functions and their aliases as documented. Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
Add note to custom-functions.md explaining that globally-registered custom plugins (via registerFunctionPlugin) are available in all instances but don't appear in static metadata API methods. Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
…dsontable/hyperformula into feature/hf-249-function-metadata-api
…(HF-249) (#1709) <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ### 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 — `SUMIF` used `Range, Criteria, Sumrange`, `SUMIFS` used `Sum_Range, Criterion_range1, Criterion1`, `AVERAGEIF` used `Range, Criterion, Average_Range` — mixing Title-case, `snake_Case`, and run-on tokens. This normalizes **every** `FunctionDoc` parameter name to `snake_case` so the catalogue is internally consistent. **What changed** - All 172 unique parameter names across the 13 `categories/*.ts` files are now `snake_case` (lowercase words joined by `_`, camelCase/acronym boundaries split, separators unified, trailing digits kept attached: `Number1`/`Number_1` → `number1`). - Run-on tokens the docs left unsplit are segmented so siblings agree: `Sumrange` → `sum_range` (matches `SUMIFS`), `Searchcriterion` → `search_criterion` (matches `VLOOKUP`/`HLOOKUP`), plus `logical_value`, `date_string`, `time_string`, `start_date`, `minimum_length`, `lower_bound`, `upper_bound`, `number_x`, `number_y`. - The migration generator (`scripts/hf249-migrate-function-docs.ts`) now emits `snake_case` via a shared, exported `toSnakeCase` helper (+ a small segmentation table), so a future regeneration stays consistent. Its `main()` is guarded with `require.main === module` so the helper can be imported without side effects. - **Not touched:** parameter `description`s, `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. - `eslint` on the changed files — 0 errors. - `test/smoke.spec.ts` — 4/4 pass. - Ad-hoc runtime checks against the built API: - every catalogue **and** `getFunctionDetails` parameter name matches `^[a-z][a-z0-9]*(_[a-z0-9]+)*$`; - names are unique within each function (no collisions introduced by the transform); - `getAvailableFunctions`/`getFunctionDetails` list↔details parity still holds; - `SUMIF` & `SUMIFS` both expose `sum_range`; `MATCH` & `VLOOKUP` both expose `search_criterion`; - custom-function details still report `Arg1`, `Arg2`. - Ran the generator to confirm it produces identical parameter names to this hand-applied transform (the only regen deltas are unrelated pre-existing drift — alphabetical re-sorting of manually-added `HSTACK`/`XIRR`, the hand-authored `SUM`/`SUMIF` fields, and a stale `TEXT` description — so the regen output was discarded). ### 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) - [ ] Additional language file, or a change to an existing language file (translations) - [ ] Change to the documentation ### Related issues: 1. Follow-up to #1692 (HF-249); targets that branch. ### Notes for reviewers - **Paired tests:** the private test suite (`hyperformula-tests#14`) asserts specific parameter names (e.g. `SUMIF` params); those expectations need updating to the new `snake_case` names. - **Open question — custom functions:** the request was "snake_case for all param names". I scoped this to the catalogue and left the runtime custom-function placeholders as the documented `Arg1`/`Arg2`. If you'd also like those lowercased (`arg1`/`arg2`), it's a 1-line change in `buildCustomFunctionDetails` plus a `custom-functions.md` wording update — say the word. - **Segmentation calls** (`sumrange`→`sum_range`, etc.) are the only editorial decisions here; everything else is mechanical. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-51ff732c-3307-42ef-aa24-7fb0dc332ce9"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-51ff732c-3307-42ef-aa24-7fb0dc332ce9"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
…es (HF-249) (#1710) <!-- CURSOR_AGENT_PR_BODY_BEGIN --> ### Context Addresses a code-review finding on #1692: `getAvailableFunctions` can throw a `RangeError` for a caller-registered, malformed language code. `buildAvailableFunctions` built its sort collator with `new Intl.Collator(toBcp47Locale(code))`. `toBcp47Locale` only hyphenates the canonical `<ll><RR>` shape and passes any other string through unchanged, and `registerLanguage` performs **no** shape validation on the code. `Intl.Collator` throws a `RangeError` on a *structurally invalid* language tag (it does **not** silently fall back to the default locale). So a caller who registers an underscore-style code such as `'pt_BR'` (or `'en_US'`, `'x'`, `'12'`) and then calls `getAvailableFunctions` (static **or** instance) crashes. Built-in language codes are all safe, so this only affects consumer-registered malformed codes → important, not critical. The `toBcp47Locale` JSDoc was also wrong: it claimed unrecognized tags "fall back to the default locale," which `Intl.Collator` does not do for invalid tags. ### Fix - Extract `createLocaleCollator(languageCode)` which wraps the `Intl.Collator` construction in a `try/catch` and falls back to the environment-default collator (`new Intl.Collator()`) on `RangeError`, so listing functions never crashes on a non-BCP-47 code. - Use it in `buildAvailableFunctions`. - Correct the `toBcp47Locale` JSDoc to describe the real behavior (invalid tags throw; the throw is handled by `createLocaleCollator`). `getFunctionDetails` is unaffected — it does not sort and never constructs a collator. ### How did you test your changes? - Reproduced the crash before the fix: registering `'pt_BR'` then calling `getAvailableFunctions('pt_BR')` threw `RangeError: Incorrect locale information provided`. - After the fix, `'pt_BR'`, `'en_US'`, `'x'`, and `'12'` each return the full, alphabetically-sorted 419-entry list; well-formed `'enGB'` is unchanged. - Confirmed via Node that `new Intl.Collator('pt_BR'|'en_US'|'x'|'12')` throw while `'zz-ZZ'`/`'qqq'` do not. - `tsc --noEmit` clean; `eslint src/HyperFormula.ts` 0 errors; `test/smoke.spec.ts` 4/4. ### 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) - [x] 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. Follow-up to #1692 (HF-249); targets that branch. ### Notes for reviewers - Behavior on a malformed code is now "sort using the default collator" rather than throwing. The alternative (validating codes in `registerLanguage`) would be a broader, potentially breaking change, so it's intentionally out of scope here. - A unit test for this belongs in the paired private suite (`hyperformula-tests`): register a `'pt_BR'`-style code and assert `getAvailableFunctions` returns a sorted list instead of throwing. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-51ff732c-3307-42ef-aa24-7fb0dc332ce9"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-51ff732c-3307-42ef-aa24-7fb0dc332ce9"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Kuba Sekowski <sequba@users.noreply.github.com>
…param backticks (HF-300) - BETA.DIST second example switched from the PDF-with-bounds form (=BETA.DIST(2,2,3,FALSE(),0,4) → HF 1.5 vs Excel 0.375, a known engine PDF-scaling bug) to the cumulative bounded form (=BETA.DIST(2,2,3,TRUE(),0,4) → 0.6875, matches Excel). Still exercises the optional A/B bounds args, but no longer ships an example whose output is wrong. - Removed backticks from six date-and-time PARAMETER descriptions (nullDate/dateFormats/timeFormats). The metadata field is plain text and every other parameter description in the catalogue is plain prose; backticks rendered literally for API consumers. shortDescription markdown links are migrated (#1692) content and left untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Source: https://app.clickup.com/t/86caprtgj ADR: adr/2026-07-13-hf300-function-metadata-enrichment.md
A traceability audit (run during HF-300) found migrated shortDescriptions that either misstate behavior or leak markup into the plain-text metadata API. Per Kuba's call (Slack #hyperformula-dev, 2026-07-15) they belong on the parent HF-249 branch, since the catalogue is becoming the single source of truth (docs/guide generated from it at build): - XNPV: was verbatim identical to NPV; now conveys its non-periodic/date-based nature. - MIRR: "modified internal value" -> "modified internal rate of return". - SPLIT/TEXT: dropped <br> + markdown (rendered literally through the API); TEXT now also mentions the stringifyCurrency option. - SECH: "given angle (in radians)" -> "given value" (the argument is a plain number). - BASE: "positive integer" -> "non-negative integer" (0 is accepted by the impl). - typos: MROUND "neares"/"multiplicity", Fischer->Fisher, densitity->density, skeweness->skewness (x2). Catalogue-only; docs/guide regenerates from these once #1699 lands. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1692 +/- ##
===========================================
- Coverage 97.16% 96.43% -0.73%
===========================================
Files 176 192 +16
Lines 15450 15589 +139
Branches 3417 3447 +30
===========================================
+ Hits 15012 15034 +22
- Misses 438 555 +117
🚀 New features to boost your workflow:
|



Summary
Adds a two-tier public API for retrieving function metadata, intended for a Formula Builder / function picker:
HyperFormula.getAvailableFunctions(code)(static) /hfInstance.getAvailableFunctions()(instance) →FunctionListEntry[]— a short list sorted alphabetically by localized name:localizedName,canonicalName,category,shortDescription.HyperFormula.getFunctionDetails(canonicalName, code)(static) /hfInstance.getFunctionDetails(canonicalName)(instance) →FunctionDetails | undefined— the same fields plus the orderedparameterslist (eachname,description,optional) andrepeatLastArgs(how many trailing parameters repeat;0for a fixed arg list). No pre-rendered syntax string — the caller renders it fromparameters+repeatLastArgs.Built-in functions and their aliases are included. Custom (user-registered) functions are instance-scoped, so they are listed by the instance methods, not the static ones; a custom function has
category: undefined, an emptyshortDescription, and positional parameter names (Arg1,Arg2, …). A custom function that shadows a built-in id is reported as the custom function (never the built-in’s catalogue metadata).Catalogue
The 363-function catalogue lives in
src/interpreter/functionMetadata/categories/(generated fromdocs/guide/built-in-functions.mdby a dev-only script; not shipped —tsconfigincludeis["src"]). Categories use the full-word names from the official Excel docs;Array manipulation,Matrix functionsandOperatorare HyperFormula-specific. Parameter names/optionality follow the implementation arity inimplementedFunctions; a catalogue-vs-implementation arity mismatch drops the function from both list and details so they never disagree.Tests
Paired tests PR: handsontable/hyperformula-tests#14 (branch
feature/hf-249-function-metadata-api):category: undefined), custom alias, instance-vs-static scopingFUNCTION_DOCSkeys == canonical set (both directions);FUNCTION_CATEGORIESshaperepeatLastArgs(SUM=1, SUMIFS=2, SUMIF=0)repeatLastArgsclamplocalizedNamewithcanonicalNametiebreaker (locale-aware), non-Latin namesMVP scope
parameters[].description,documentationUrl, andexamplesare present but empty; authored in a later phase.Notes
canonicalNametiebreaker, viaIntl.Collatorfrom the language’s locale (deterministic across environments).Note
Medium Risk
Large new public API surface and registry ownership logic, but changes are additive read-only metadata with guards for shadowed built-ins and catalogue drift rather than formula evaluation paths.
Overview
Adds a function metadata API for building function pickers and reference UIs:
getAvailableFunctions()returns a localized, sorted short list;getFunctionDetails()returns full details including parameters, optionality, andrepeatLastArgs(callers build syntax strings themselves). Both exist as static methods (built-ins + aliases, explicit language code) and instance methods (instance registry + configured language, including custom plugins).Introduces a
FUNCTION_DOCScatalogue undersrc/interpreter/functionMetadata/(per-category files, mostly generated fromdocs/guide/built-in-functions.mdvia dev scriptscripts/hf249-migrate-function-docs.ts).FunctionRegistrygains built-in ownership tracking (captureBuiltinFunctionOwners,isBuiltinFunction,getListableFunctionIds) so custom plugins that shadow a built-in id are described as custom functions, not with stale built-in docs; catalogue arity drift omits a function from list and details.Docs/changelog updated;
SUM/SUMIFcarry sample rich metadata (examples, param descriptions) for Formula Builder testing.Reviewed by Cursor Bugbot for commit 40b6468. Bugbot is set up for automated code reviews on this repo. Configure here.