/** * The contract the extracted expression-emitter modules depend on instead of * the concrete `JinjaAdapter`. * * Ported from `packages/adapter-xslate/src/adapter/emit-context.ts`. The * Jinja adapter's top-level expression lowering is mutually recursive with * the adapter's own const/record resolution and its filter-predicate * emitter, so the extracted `JinjaTopLevelEmitter` still needs to call back * into shared per-compile state and recursive entry points. * `JinjaEmitContext` is that seam: the emitter takes a `JinjaEmitContext` * built by the adapter's private `emitCtx` getter (the adapter does NOT * `implements` this interface, so the wrapped members stay private and off * its exported public type). The emitter depends on this narrow interface * rather than the full class, so the coupling is explicit and it's * unit-testable against a stub. * * Keep this surface minimal: add a member only when an extracted module * genuinely needs it, so the seam documents the real cross-module coupling * rather than re-exposing the whole adapter. */ import type { ParsedExpr, CompilerError, IRMetadata, LoweringMatcher, ArithmeticCoercion } from '@barefootjs/jsx' export interface JinjaEmitContext { /** * (#1922) Local binding names the request-scoped `searchParams()` env signal * is imported under. Non-empty enables the env-signal method-call lowering. */ readonly _searchParamsLocals: Set /** The undestructured props parameter's name (`props`), or `null` when the props are destructured. */ readonly _propsObjectName: string | null /** Whether `name` read here is a JavaScript global (`ComputedConstContext.isGlobal`), #3505. */ readonly _isGlobalName: (name: string) => boolean /** * Registered lowering-plugin matchers (#2057), bound to this component's * metadata at init. Read by `JinjaTopLevelEmitter`'s `lowering` seam * (#2843) so a registered call — the built-in `queryHref`, or any * userland plugin — is recognised no matter where it sits in an * expression tree (a ternary branch, a template-literal interpolation, …), * not only when it's the call the adapter's own top-level conversion * entry point (`convertExpressionToJinja`) is asked to lower directly. */ readonly _loweringMatchers: readonly LoweringMatcher[] /** * Inline a module-scope pure string-literal const by name as the resolved * literal value, or null when the name is not such a const. */ _resolveModuleStringConst(name: string): string | null /** Resolve a literal const (`const totalPages = 5`) to its Jinja value, or null. */ _resolveLiteralConst(name: string): string | null /** * The template variable an explicit `props.` read resolves to here: * its root alias while a loop binding shadows `name` (#3314), else `name`. */ _rootPropReadName(name: string): string /** * Resolve a static property access on a module object-literal const * (`variantClasses.ghost`) to its Jinja value at compile time, or null. */ _resolveStaticRecordLiteral(objectName: string, key: string): string | null /** Record a BF101 unsupported-expression diagnostic. */ /** * #3144: whether `name` is a component-body local bound to an opaque * call (`const label = makeLabel()`) -- invoking it (`label()`) has no * Jinja template lowering, unlike a genuine signal/memo getter. See * `isOpaqueLocalAccessorName` (`@barefootjs/jsx`). */ _isOpaqueLocalAccessorCall(name: string): boolean /** How the emitter coerces an arithmetic operand over a nullish value * (`arithmeticOperandCoercion`, #3474): `'null'` reads nil as `0`, * `'undefined'` as the runtime's JS `Number()` gives it, `NaN`. `other` is * omitted for unary minus (#3533). */ _arithmeticCoercion(op: string, operand: ParsedExpr, other?: ParsedExpr): ArithmeticCoercion _recordExprBF101(message: string, reason?: string): void /** Lower a filter/predicate body to its Jinja form, bound to `param`. */ _renderJinjaFilterExprPublic(expr: ParsedExpr, param: string): string } /** * The contract the extracted object-literal / conditional-spread lowering * (`spread/spread-codegen.ts`) depends on. Declared separately from * `JinjaEmitContext` so each extracted module's real coupling is documented * precisely. Mirror of the Xslate adapter's `XslateSpreadContext`. */ export interface JinjaSpreadContext { /** Component name, for diagnostic source locations. */ readonly componentName: string /** Per-compile diagnostic list the spread lowering appends to. */ readonly errors: CompilerError[] /** Local-constant metadata, for resolving `Record[key]` spread values. */ readonly localConstants: IRMetadata['localConstants'] /** Prop params, for classifying a bare-identifier index as a prop. */ readonly propsParams: { name: string }[] /** * Lower a JS expression to its Jinja form (the core recursive entry). * * When the IR already carries a structured `ParsedExpr` tree, pass it as * `preParsed` so the converter threads it straight through instead of * re-parsing `expr`. With `preParsed` set, `expr` is unused for parsing * (the converter derives any diagnostic text from the tree), so callers * may pass `''`. */ convertExpressionToJinja(expr: string, preParsed?: ParsedExpr): string /** * Lower a JS expression to a Jinja CONDITION (routes through `bf.truthy` * unless the expression is structurally already boolean-shaped — see * `@barefootjs/jsx`'s `adapters/boolean-result.ts`). Used for the conditional-spread ternary's test, * which is a condition position, not a value position. Same `preParsed` * contract as `convertExpressionToJinja`. */ convertConditionToJinja(expr: string, preParsed?: ParsedExpr): string } /** * The contract the extracted in-template memo / context seeding * (`memo/seed.ts`) depends on. The seed lowering recurses into the core * expression lowering to compute a derived signal/memo value or a context * default; that recursive entry is its only adapter coupling. */ export interface JinjaMemoContext { /** * Lower a JS expression to its Jinja form (the core recursive entry). See * `JinjaSpreadContext.convertExpressionToJinja` for the `preParsed` contract. * `pos` (default `'rendered'`) must be `'value'` for a derived-seed RHS — * an assignment, not a render — so the internal support gate matches the * `isSupportedValue` classification the seed plan already used. */ convertExpressionToJinja(expr: string, preParsed?: ParsedExpr, pos?: 'rendered' | 'value'): string /** * Per-compile diagnostic list `convertExpressionToJinja` appends to on an * unsupported shape (`_recordExprBF101`). `memo/seed.ts`'s * `generateDerivedMemoSeed` is a SPECULATIVE "try this in-template * recomputation, else fall back to the static ssrDefault seed" attempt per * plan step — unlike every other `convertExpressionToJinja` call site, a * failure here must NOT become a hard compile error, so it snapshots this * array's length before calling in and truncates back to it on failure * (discarding whatever `_recordExprBF101` appended) rather than letting * the error escape. */ readonly errors: CompilerError[] }