UNPKG

messageformat

Version:

Intl.MessageFormat / Unicode MessageFormat 2 parser, runtime and polyfill

105 lines (104 loc) 3.37 kB
import { MessageResolutionError } from "../errors.js"; import { fallback } from "../functions/fallback.js"; import { unknown } from "../functions/unknown.js"; import { MessageFunctionContext } from "./function-context.js"; import { resolveExpression } from "./resolve-expression.js"; /** * Declarations aren't resolved until they're requierd, * and their resolution order matters for variable resolution. * This internal class is used to store any required data, * and to allow for `instanceof` detection. * * @internal */ export class UnresolvedExpression { expression; scope; constructor(expression, scope) { this.expression = expression; this.scope = scope; } } const isScope = (scope) => scope !== null && (typeof scope === 'object' || typeof scope === 'function'); /** * Looks for the longest matching `.` delimited starting substring of name. * @returns `undefined` if value not found */ function getValue(scope, name) { if (isScope(scope)) { if (name in scope) return scope[name]; const parts = name.split('.'); for (let i = parts.length - 1; i > 0; --i) { const head = parts.slice(0, i).join('.'); if (head in scope) { const tail = parts.slice(i).join('.'); return getValue(scope[head], tail); } } for (const [key, value] of Object.entries(scope)) { if (key.normalize() === name) return value; } } return undefined; } /** * Get the raw value of a variable. * Resolves declarations as necessary * * @internal * @returns `unknown` or `any` for input values; * `MessageValue` for `.input` and `.local` declaration values. */ export function lookupVariableRef(ctx, { name }) { const value = getValue(ctx.scope, name); if (value === undefined) { const source = '$' + name; const msg = `Variable not available: ${source}`; ctx.onError(new MessageResolutionError('unresolved-variable', msg, source)); } else if (value instanceof UnresolvedExpression) { const local = resolveExpression(value.scope ? { ...ctx, scope: value.scope } : ctx, value.expression); ctx.scope[name] = local; ctx.localVars.add(local); return local; } return value; } export function resolveVariableRef(ctx, ref) { const source = '$' + ref.name; const value = lookupVariableRef(ctx, ref); if (value === undefined) return fallback(source); let type = typeof value; if (type === 'object') { const mv = value; if (mv.type === 'fallback') return fallback(source); if (ctx.localVars.has(mv)) { mv.source = source; return mv; } if (value instanceof Number) type = 'number'; else if (value instanceof String) type = 'string'; } let msgFn; switch (type) { case 'bigint': case 'number': msgFn = ctx.functions.number; break; case 'string': msgFn = ctx.functions.string; break; default: return unknown(source, value); } const msgCtx = new MessageFunctionContext(ctx, source); const mv = msgFn(msgCtx, {}, value); mv.source = source; return mv; }