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)
- 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. - 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
Out-of-scope finding from #5343, measured while replacing the fabricated
getExpressionEvaluator()example incontent/docs/guide/expressions.mdwith 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 builtpackages/core/dist/)Mechanism
ExpressionEvaluator.registerFunction(name, fn)delegates toFormulaFunctions.register, which stores undername.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.formulas.toObject(), which hands over the stored (upper-cased) keys (FormulaFunctions.ts:66), merged under the context inevaluateExpression(ExpressionEvaluator.ts:159).evaluate()catches,console.warns and returnsdefaultValue ?? 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)
registerFunction(and say the call site must use the upper-case name) — cheapest, and consistent with the formula vocabulary.registerFunctioncase-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 caseover open issues: zero hits.Generated by Claude Code