Skip to content

finding(core): ExpressionEvaluator.registerFunction upper-cases the name, so a lower-case call in an expression renders its own ${...} source #5363

Description

@os-support-ai

Out-of-scope finding from #5343, measured while replacing the fabricated getExpressionEvaluator() example in content/docs/guide/expressions.md with the real surface. Filed unassigned, not claiming. Observation-class: nothing is broken for the built-in formula names, but the public method's contract is silently case-folding.

Measured, not inferred (origin/main = bdf8cf76e, run against the built packages/core/dist/)

const fmt = (v) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(v);
evaluateExpression('${formatCurrency(price)}', { formatCurrency: fmt, price: 1234.5 })
→ '$1,234.50' // context-supplied function: exact name, works
const ev = new ExpressionEvaluator({ price: 1234.5 });
ev.registerFunction('formatCurrency', fmt);
ev.evaluate('${formatCurrency(price)}') → '${formatCurrency(price)}' // the SOURCE, verbatim
ev.evaluate('${FORMATCURRENCY(price)}') → '$1,234.50'

Mechanism

  • ExpressionEvaluator.registerFunction(name, fn) delegates to FormulaFunctions.register, which stores under name.toUpperCase() (packages/core/src/evaluator/FormulaFunctions.ts:38). That is right for the built-ins — they are spreadsheet-style names (SUM, IF) — but the public method is spelled like "register a JS function" and the fold is silent.
  • The lookup that reaches an expression is formulas.toObject(), which hands over the stored (upper-cased) keys (FormulaFunctions.ts:66), merged under the context in evaluateExpression (ExpressionEvaluator.ts:159).
  • The failure is then absorbed: evaluate() catches, console.warns and returns defaultValue ?? expression (ExpressionEvaluator.ts:~448), so the reader sees the raw ${formatCurrency(price)} text rendered on screen rather than an error. The soft-fail itself looks deliberate; it is what makes the case-fold hard to notice.

Two candidate directions (not pre-judged)

  1. Keep the fold, document it on registerFunction (and say the call site must use the upper-case name) — cheapest, and consistent with the formula vocabulary.
  2. Make registerFunction case-preserving for the name it is given while keeping the built-ins' case-insensitive lookup — a behaviour change on a public method; needs a decision, not a drive-by.

The doc side is already settled independently: #5343 rewrote that guide section to teach context-supplied functions (exact name, verified above), so no documentation currently depends on registerFunction.

Searched for duplicates

registerFunction, formula uppercase, expression evaluator case over open issues: zero hits.


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions