Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
64746c1
feat(metadata): add formatFunctionSyntax helper (HF-249)
marcin-kordas-hoc Jun 29, 2026
014d735
feat(metadata): add pure built-in functions table renderer and marker…
marcin-kordas-hoc Jun 29, 2026
366e6f5
docs(guide): add autogeneration markers around the functions table (H…
marcin-kordas-hoc Jun 29, 2026
6111bb9
docs(guide): keep functions-count line outside the autogeneration mar…
marcin-kordas-hoc Jun 29, 2026
3738a50
docs(guide): place do-not-edit note above the START marker so regener…
marcin-kordas-hoc Jun 29, 2026
080838c
feat(docs): generate built-in functions table from HF API (HF-249)
marcin-kordas-hoc Jun 29, 2026
2ec0142
feat(metadata): surface OFFSET and VERSION via the function-metadata …
marcin-kordas-hoc Jul 1, 2026
f52cbaa
docs(guide): regenerate built-in functions table to include OFFSET an…
marcin-kordas-hoc Jul 1, 2026
b8b701f
feat(docs): fail the drift check when the function set changes (HF-249)
marcin-kordas-hoc Jul 1, 2026
f25e4c8
style(docs): join id arrays in drift-check message to satisfy lint (H…
marcin-kordas-hoc Jul 1, 2026
4d228dc
build(docs): add docs:generate-functions and drift-check scripts (HF-…
marcin-kordas-hoc Jul 1, 2026
0fce9e2
docs(dev): avoid asserting CI wiring not yet added (HF-249)
marcin-kordas-hoc Jul 1, 2026
52f1096
ci(docs): fail build-docs if the functions table is stale (HF-249)
marcin-kordas-hoc Jul 1, 2026
cfa6cd8
fix(metadata): break renderer ties by canonicalName, soften stale pro…
marcin-kordas-hoc Jul 1, 2026
18615a3
docs(metadata): drop internal spec ref, note English-only collator (H…
marcin-kordas-hoc Jul 1, 2026
bb9ef76
fix(metadata): keep static getFunctionDetails protected-aware after #…
marcin-kordas-hoc Jul 15, 2026
0fd31f7
build(docs): regenerate the functions table at docs:build; drop the C…
marcin-kordas-hoc Jul 15, 2026
66a5da5
feat(metadata): strip doc markup from getFunctionDetails shortDescrip…
marcin-kordas-hoc Jul 15, 2026
3d7bbe6
chore(metadata): remove the one-time migration script and its danglin…
marcin-kordas-hoc Jul 15, 2026
f6dde48
fix(metadata): snake_case OFFSET parameter names to match the catalog…
marcin-kordas-hoc Jul 15, 2026
490c6eb
docs(guide): regenerate built-in functions table from the catalogue (…
marcin-kordas-hoc Jul 15, 2026
7ae63e8
fix(metadata): keep protected metadata resolution fail-safe when stru…
marcin-kordas-hoc Jul 15, 2026
b1098fd
chore(docs): drop the vestigial --check mode from the functions-table…
marcin-kordas-hoc Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Added the `getAvailableFunctions()` and `getFunctionDetails()` methods (both static and instance) for retrieving function metadata. [#1692](https://github.com/handsontable/hyperformula/pull/1692)
- Added new functions: VSTACK, HSTACK. [#1698](https://github.com/handsontable/hyperformula/pull/1698)
- Added a new function: `XIRR`. [#1701](https://github.com/handsontable/hyperformula/pull/1701)
- The built-in functions guide table is now generated from the function metadata API (single source of truth), and now also documents OFFSET and VERSION. [#1692](https://github.com/handsontable/hyperformula/pull/1692)
- Added an Indonesian (Bahasa Indonesia) language pack. [#1674](https://github.com/handsontable/hyperformula/pull/1674)
- Added a `stringifyCurrency` config option that lets you plug in a custom currency formatter for the `TEXT` function. [#1145](https://github.com/handsontable/hyperformula/issues/1145)

Expand Down
6 changes: 6 additions & 0 deletions DEV_DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ Adding a built-in function is similar to adding a [custom function](docs/guide/c
4. Add translations to all language files in `src/i18n/languages/`.
5. Add tests in `test/unit/interpreter/`.

The built-in functions table in `docs/guide/built-in-functions.md` is generated from HyperFormula's API
(`getAvailableFunctions`/`getFunctionDetails`). Do not hand-edit the region between the
`AUTOGENERATED:FUNCTIONS` markers — change the metadata in `src/interpreter/functionMetadata/`. The table is
regenerated automatically as part of `npm run docs:build` (and `npm run docs:dev`), so the committed snapshot never
drifts; run `npm run docs:generate-functions` if you want to refresh it on its own.

## Internationalization and function translations

HyperFormula supports internationalization and provides localized function names for all built-in languages. Translation files live in `src/i18n/languages/`. New functions must include translations for all built-in languages.
Expand Down
935 changes: 456 additions & 479 deletions docs/guide/built-in-functions.md

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@
"unpkg": "dist/hyperformula.min.js",
"typings": "./typings/index.d.ts",
"scripts": {
"docs:dev": "npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress dev docs --silent --no-clear-screen --no-cache",
"docs:build": "npm run bundle-all && npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress build docs",
"docs:dev": "npm run docs:generate-functions && npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress dev docs --silent --no-clear-screen --no-cache",
"docs:build": "npm run docs:generate-functions && npm run bundle-all && npm run typedoc:build-api && cross-env NODE_OPTIONS=--openssl-legacy-provider vuepress build docs",
"docs:code-examples:generate-js": "bash docs/code-examples-generator.sh",
"docs:code-examples:generate-all-js": "bash docs/code-examples-generator.sh --generateAll",
"docs:code-examples:format-all-ts": "bash docs/code-examples-generator.sh --formatAllTsExamples",
"snippets:extract": "node script/extract-doc-snippets.js",
"docs:generate-functions": "npm run tsnode scripts/generate-builtin-functions-doc.ts",
Comment thread
marcin-kordas-hoc marked this conversation as resolved.
"bundle-all": "cross-env HF_COMPILE=1 npm-run-all clean compile bundle:** verify-bundles",
"bundle:es": "(node script/if-ne-env.js HF_COMPILE=1 || npm run compile) && cross-env-shell BABEL_ENV=es env-cmd -f ht.config.js babel lib --out-file-extension .mjs --out-dir es",
"bundle:cjs": "(node script/if-ne-env.js HF_COMPILE=1 || npm run compile) && cross-env-shell BABEL_ENV=commonjs env-cmd -f ht.config.js babel lib --out-dir commonjs",
Expand Down
37 changes: 37 additions & 0 deletions scripts/generate-builtin-functions-doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @license
* Copyright (c) 2025 Handsoncode. All rights reserved.
*/

/**
* HF-249 bullet 3 — generates the built-in functions table in `docs/guide/built-in-functions.md` from
* HyperFormula's public API (single source of truth). Dev-only; never shipped (`tsconfig.json` `include` is
* `["src"]`). Run via `npm run docs:generate-functions`; it also runs as the first step of `docs:build` and
* `docs:dev`, so the committed table is regenerated on every build and cannot drift from the catalogue.
*/

import * as fs from 'fs'
import * as path from 'path'
import {HyperFormula} from '../src'
import {renderBuiltinFunctionsTable, spliceFunctionsTable} from '../src/interpreter/functionMetadata/renderBuiltinFunctionsTable'

const REPO_ROOT = path.resolve(__dirname, '..')
const DOC_PATH = path.join(REPO_ROOT, 'docs/guide/built-in-functions.md')
const LANGUAGE = 'enGB'

/** Reads the doc file and returns a new version with the generated table region spliced in. */
function buildUpdatedFile(): string {
const entries = HyperFormula.getAvailableFunctions(LANGUAGE)
const detailsFor = (canonicalName: string) => HyperFormula.getFunctionDetails(canonicalName, LANGUAGE)
const generated = renderBuiltinFunctionsTable(entries, detailsFor)
const current = fs.readFileSync(DOC_PATH, 'utf8')
return spliceFunctionsTable(current, generated)
}

/** Regenerates the table region of the doc file from the current catalogue. */
function main(): void {
fs.writeFileSync(DOC_PATH, buildUpdatedFile(), 'utf8')
process.stdout.write('built-in-functions.md regenerated.\n')
}

main()
276 changes: 0 additions & 276 deletions scripts/hf249-migrate-function-docs.ts

This file was deleted.

22 changes: 21 additions & 1 deletion src/HyperFormula.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {FunctionPluginDefinition} from './interpreter'
import {FUNCTION_DOCS} from './interpreter/functionMetadata'
import {buildCustomFunctionDetails, buildCustomFunctionListEntry, buildFunctionDetails, buildFunctionListEntry, StructuralMetadata} from './interpreter/functionMetadata/buildFunctionDescriptions'
import {FunctionDetails, FunctionDoc, FunctionListEntry} from './interpreter/functionMetadata/FunctionDescription'
import {PROTECTED_FUNCTION_METADATA} from './interpreter/functionMetadata/protectedFunctionMetadata'
import {FunctionRegistry, FunctionTranslationsPackage} from './interpreter/FunctionRegistry'
import {FormatInfo} from './interpreter/InterpreterValue'
import {LazilyTransformingAstService} from './LazilyTransformingAstService'
Expand Down Expand Up @@ -713,7 +714,16 @@ export class HyperFormula implements TypedEmitter {
validateArgToType(code, 'string', 'code')
const plugin = FunctionRegistry.getFunctionPlugin(canonicalName)
if (plugin === undefined) {
return undefined
// Protected ids (VERSION, OFFSET) have no registered plugin by design (`getFunctionPlugin` always returns
// `undefined` for them), but the metadata API still describes them — resolved from the authored catalogue
// doc, so getFunctionDetails agrees with getAvailableFunctions. Anything else with no plugin is genuinely
// unknown. `buildFunctionDetailsFor` returns `undefined` for a protected id without a catalogue doc, so the
// list and the details stay consistent for those too.
if (!FunctionRegistry.functionIsProtected(canonicalName)) {
return undefined
}
const language = this.getLanguage(code)
return HyperFormula.buildFunctionDetailsFor(canonicalName, undefined, language)
}
// Resolve aliases to their canonical target id before checking built-in ownership
const canonicalId = plugin.aliases?.[canonicalName] ?? canonicalName
Expand All @@ -738,6 +748,16 @@ export class HyperFormula implements TypedEmitter {
* @param {FunctionPluginDefinition | undefined} plugin - the plugin registered for `functionId`, or `undefined`
*/
private static resolveFunctionMetadata(functionId: string, plugin: FunctionPluginDefinition | undefined): { doc: FunctionDoc | undefined, metadata: StructuralMetadata } | undefined {
// Protected ids (VERSION, OFFSET) are excluded from the plugin registry by design (`getFunctionPlugin` always
// returns `undefined` for them), so they would otherwise fall straight into the `plugin === undefined` case
// below and disappear from the metadata API. Kuba decided (HF-249) that they must still be described, because
// a user can call them from a formula: resolve them here from the authored catalogue doc and structural
// metadata instead of from a plugin. A protected id stays unlisted unless BOTH are authored — requiring the
// structural metadata keeps `buildFunctionDetails` from reading `repeatLastArgs`/`parameters` off `undefined`
// if the two maps ever drift (fail-safe: the function is omitted rather than crashing the metadata API).
if (FunctionRegistry.functionIsProtected(functionId) && FUNCTION_DOCS[functionId] !== undefined && PROTECTED_FUNCTION_METADATA[functionId] !== undefined) {
return {doc: FUNCTION_DOCS[functionId], metadata: PROTECTED_FUNCTION_METADATA[functionId]}
Comment thread
marcin-kordas-hoc marked this conversation as resolved.
}
if (plugin === undefined) {
return undefined
}
Expand Down
Loading
Loading