@barefootjs/mojolicious 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/adapter/analysis/component-tree.d.ts +30 -0
  2. package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
  3. package/dist/adapter/emit-context.d.ts +96 -0
  4. package/dist/adapter/emit-context.d.ts.map +1 -0
  5. package/dist/adapter/expr/array-method.d.ts +85 -0
  6. package/dist/adapter/expr/array-method.d.ts.map +1 -0
  7. package/dist/adapter/expr/emitters.d.ts +78 -0
  8. package/dist/adapter/expr/emitters.d.ts.map +1 -0
  9. package/dist/adapter/expr/operand.d.ts +34 -0
  10. package/dist/adapter/expr/operand.d.ts.map +1 -0
  11. package/dist/adapter/index.js +1482 -1339
  12. package/dist/adapter/lib/constants.d.ts +27 -0
  13. package/dist/adapter/lib/constants.d.ts.map +1 -0
  14. package/dist/adapter/lib/ir-scope.d.ts +33 -0
  15. package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
  16. package/dist/adapter/lib/perl-naming.d.ts +29 -0
  17. package/dist/adapter/lib/perl-naming.d.ts.map +1 -0
  18. package/dist/adapter/lib/types.d.ts +28 -0
  19. package/dist/adapter/lib/types.d.ts.map +1 -0
  20. package/dist/adapter/memo/seed.d.ts +34 -0
  21. package/dist/adapter/memo/seed.d.ts.map +1 -0
  22. package/dist/adapter/mojo-adapter.d.ts +46 -104
  23. package/dist/adapter/mojo-adapter.d.ts.map +1 -1
  24. package/dist/adapter/props/prop-classes.d.ts +48 -0
  25. package/dist/adapter/props/prop-classes.d.ts.map +1 -0
  26. package/dist/adapter/spread/spread-codegen.d.ts +64 -0
  27. package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
  28. package/dist/adapter/value/parsed-literal.d.ts +26 -0
  29. package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
  30. package/dist/build.js +1482 -1339
  31. package/dist/index.js +1482 -1339
  32. package/dist/test-render.d.ts.map +1 -1
  33. package/lib/BarefootJS/Backend/Mojo.pm +1 -1
  34. package/lib/Mojolicious/Plugin/BarefootJS/DevReload.pm +1 -1
  35. package/lib/Mojolicious/Plugin/BarefootJS.pm +1 -1
  36. package/package.json +3 -3
  37. package/src/__tests__/mojo-adapter.test.ts +286 -69
  38. package/src/__tests__/query-href.test.ts +94 -0
  39. package/src/adapter/analysis/component-tree.ts +128 -0
  40. package/src/adapter/emit-context.ts +107 -0
  41. package/src/adapter/expr/array-method.ts +429 -0
  42. package/src/adapter/expr/emitters.ts +639 -0
  43. package/src/adapter/expr/operand.ts +55 -0
  44. package/src/adapter/lib/constants.ts +39 -0
  45. package/src/adapter/lib/ir-scope.ts +57 -0
  46. package/src/adapter/lib/perl-naming.ts +36 -0
  47. package/src/adapter/lib/types.ts +31 -0
  48. package/src/adapter/memo/seed.ts +73 -0
  49. package/src/adapter/mojo-adapter.ts +248 -1485
  50. package/src/adapter/props/prop-classes.ts +87 -0
  51. package/src/adapter/spread/spread-codegen.ts +181 -0
  52. package/src/adapter/value/parsed-literal.ts +34 -0
  53. package/src/test-render.ts +2 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Operand-type classification + index-access lowering for the Mojolicious
3
+ * EP template adapter.
4
+ *
5
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
6
+ * track D). Pure functions over `ParsedExpr` — they take an `isStringName`
7
+ * predicate (supplied by the emitter from adapter state) rather than reading
8
+ * adapter instance state directly.
9
+ *
10
+ * SHARED CANDIDATE: `isStringTypedOperand` is byte-identical to the Xslate
11
+ * adapter's copy and is adapter-agnostic — an extraction candidate for a
12
+ * shared Perl-family codegen module (groundwork for the future Perl evaluator
13
+ * integration, issue #2018 track D). `emitIndexAccessPerl` stays Mojo-specific
14
+ * (Perl's `->[]` vs `->{}` split has no Kolon equivalent).
15
+ */
16
+
17
+ import type { ParsedExpr } from '@barefootjs/jsx'
18
+
19
+ /**
20
+ * Whether a comparison operand is string-typed, so JS `===`/`!==` against it
21
+ * must lower to Perl `eq`/`ne` instead of numeric `==`/`!=` (#1672). Covers a
22
+ * string literal, a string-signal getter call (`sel()`), and a string prop
23
+ * access (`props.x`). `isStringName` reports whether a getter/prop name is
24
+ * known-string. Loop-element fields (`t.id`) on untyped arrays have no known
25
+ * type and stay undetected — a separate, narrower gap.
26
+ */
27
+ export function isStringTypedOperand(expr: ParsedExpr, isStringName: (n: string) => boolean): boolean {
28
+ if (expr.kind === 'literal' && expr.literalType === 'string') return true
29
+ if (expr.kind === 'call' && expr.callee.kind === 'identifier' && expr.args.length === 0) {
30
+ return isStringName(expr.callee.name)
31
+ }
32
+ if (expr.kind === 'member' && expr.object.kind === 'identifier' && expr.object.name === 'props') {
33
+ return isStringName(expr.property)
34
+ }
35
+ return false
36
+ }
37
+
38
+ /**
39
+ * Lower `arr[index]` to a Perl deref. Perl distinguishes array
40
+ * (`->[$i]`) from hash (`->{$k}`) access, which JS's single `[]` does
41
+ * not — so we pick by the index expression's type: a string-typed key
42
+ * derefs the hash, anything else (the common loop-index / arithmetic
43
+ * case, e.g. `selected()[index]`) derefs the array. #1897.
44
+ */
45
+ export function emitIndexAccessPerl(
46
+ object: ParsedExpr,
47
+ index: ParsedExpr,
48
+ emit: (e: ParsedExpr) => string,
49
+ isStringName: (n: string) => boolean,
50
+ ): string {
51
+ const i = emit(index)
52
+ return isStringTypedOperand(index, isStringName)
53
+ ? `${emit(object)}->{${i}}`
54
+ : `${emit(object)}->[${i}]`
55
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Compile-time constant tables for the Mojolicious EP template adapter.
3
+ *
4
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
5
+ * track D).
6
+ */
7
+
8
+ import type { PrimitiveSpec } from './types.ts'
9
+
10
+ /**
11
+ * Single source of truth for the Mojolicious adapter's
12
+ * template-primitive surface. Each entry pairs the expected arity
13
+ * with the emit function. Adding / removing a primitive is a
14
+ * one-line change.
15
+ *
16
+ * The emit fn returns a Perl expression (no surrounding `<%= %>`)
17
+ * suitable for embedding inside the Mojo template action —
18
+ * `bf->json($val)`, `bf->floor($val)`, etc. Args arrive already
19
+ * Perl-rendered via `convertExpressionToPerl` recursion, so a
20
+ * caller passing `props.config` reaches the emit fn as `$config`.
21
+ */
22
+ export const MOJO_TEMPLATE_PRIMITIVES: Record<string, PrimitiveSpec> = {
23
+ 'JSON.stringify': { arity: 1, emit: (args) => `bf->json(${args[0]})` },
24
+ 'String': { arity: 1, emit: (args) => `bf->string(${args[0]})` },
25
+ 'Number': { arity: 1, emit: (args) => `bf->number(${args[0]})` },
26
+ 'Math.floor': { arity: 1, emit: (args) => `bf->floor(${args[0]})` },
27
+ 'Math.ceil': { arity: 1, emit: (args) => `bf->ceil(${args[0]})` },
28
+ 'Math.round': { arity: 1, emit: (args) => `bf->round(${args[0]})` },
29
+ }
30
+
31
+ /**
32
+ * Module-scope `templatePrimitives` map derived once from the spec
33
+ * record. Per-instance derivation would re-build the same Map on
34
+ * every `new MojoAdapter()` call.
35
+ */
36
+ export const MOJO_PRIMITIVE_EMIT_MAP: Record<string, (args: string[]) => string> =
37
+ Object.fromEntries(
38
+ Object.entries(MOJO_TEMPLATE_PRIMITIVES).map(([k, v]) => [k, v.emit])
39
+ )
@@ -0,0 +1,57 @@
1
+ /**
2
+ * IR traversal helpers for the Mojolicious EP template adapter.
3
+ *
4
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
5
+ * track D). Pure functions over the IR tree — no adapter instance state.
6
+ *
7
+ * SHARED CANDIDATE: the bodies here are byte-identical to the Xslate
8
+ * adapter's `lib/ir-scope.ts`. They are adapter-agnostic (no Perl/Kolon
9
+ * specifics), so they are the obvious first extraction into a shared
10
+ * Perl-family codegen module once one exists — the groundwork for the
11
+ * future Perl evaluator integration (issue #2018 track D). Kept per-adapter
12
+ * for now, matching the repo convention (the Go adapter keeps its own copy).
13
+ */
14
+
15
+ import type { IRNode, IRProp, IRIfStatement, IRFragment } from '@barefootjs/jsx'
16
+
17
+ /**
18
+ * Find the `children` prop's `jsx-children` payload (#1326). Narrowed
19
+ * via the AttrValue `kind` discriminator so adapter code stays type-
20
+ * safe if the IR shape evolves — adding a new AttrValue variant or
21
+ * renaming `children` to `jsxChildren` becomes a TS compile error
22
+ * here instead of silently dropping the children at runtime.
23
+ */
24
+ export function resolveJsxChildrenProp(props: readonly IRProp[]): IRNode[] {
25
+ const prop = props.find(p => p.name === 'children')
26
+ if (!prop) return []
27
+ if (prop.value.kind !== 'jsx-children') return []
28
+ return prop.value.children
29
+ }
30
+
31
+ /**
32
+ * Collect the component's root scope element node(s) — the elements that
33
+ * become the rendered root and so carry `data-key` for a keyed loop item. A
34
+ * plain element root is itself; an `if-statement` (early-return) root
35
+ * contributes the top element of each branch (`consequent` + the `alternate`
36
+ * chain), since exactly one branch renders at runtime. Non-element branch
37
+ * tops (fragments / nested shapes) are walked one level so an
38
+ * `if (…) return <A/>` still resolves to `<A>`. (#1297)
39
+ */
40
+ export function collectRootScopeNodes(node: IRNode): Set<IRNode> {
41
+ const out = new Set<IRNode>()
42
+ const visit = (n: IRNode | null): void => {
43
+ if (!n) return
44
+ if (n.type === 'element') { out.add(n); return }
45
+ if (n.type === 'if-statement') {
46
+ const s = n as IRIfStatement
47
+ visit(s.consequent)
48
+ visit(s.alternate)
49
+ return
50
+ }
51
+ if (n.type === 'fragment') {
52
+ for (const c of (n as IRFragment).children) visit(c)
53
+ }
54
+ }
55
+ visit(node)
56
+ return out
57
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Perl identifier / hash-key conventions for the Mojolicious EP adapter.
3
+ *
4
+ * Pure helpers extracted from `mojo-adapter.ts` (domain-module refactor,
5
+ * issue #2018 track D): none read adapter instance state, so they live at
6
+ * module scope as the single source of truth for Perl-identifier quoting
7
+ * and marker-id encoding.
8
+ */
9
+
10
+ /**
11
+ * (#checkbox) Quote a `render_child` named-arg / hashref key when it isn't a
12
+ * bare Perl identifier. A JSX attribute name like `data-slot` would otherwise
13
+ * emit `data-slot => '...'`, which Perl parses as the subtraction
14
+ * `data - slot`. Identifier-safe names (`className`, `size`, `_bf_slot`) pass
15
+ * through unquoted to keep the generated template readable.
16
+ */
17
+ export function perlHashKey(name: string): string {
18
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) ? name : `'${name.replace(/'/g, "\\'")}'`
19
+ }
20
+
21
+ /**
22
+ * Encode an `IRLoop.markerId` into a Perl-identifier-safe suffix
23
+ * for the `bf_iter_…` hoist var. Collision-free for marker ids
24
+ * that differ in any character — `-` and `_` map to distinct
25
+ * encodings (`_x2d` vs `__`) so `l-0` and `l_0` stay distinct.
26
+ *
27
+ * Today the IR only emits `l<digits>` so the encoding is mostly
28
+ * an identity, but pinning collision-freeness up front avoids a
29
+ * silent variable-shadow bug if a future marker generator widens
30
+ * the alphabet.
31
+ */
32
+ export function perlIdentifierFromMarkerId(markerId: string): string {
33
+ return markerId.replace(/[^a-zA-Z0-9]/g, (ch) =>
34
+ ch === '_' ? '__' : `_x${ch.charCodeAt(0).toString(16)}`
35
+ )
36
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared type aliases for the Mojolicious EP template adapter.
3
+ *
4
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
5
+ * track D). Pure type declarations — no runtime behaviour — so the
6
+ * extracted emit modules and the main adapter share one definition
7
+ * rather than re-declaring the render context / options shape.
8
+ */
9
+
10
+ /** A template-primitive spec: expected call arity + the emit fn. */
11
+ export interface PrimitiveSpec {
12
+ arity: number
13
+ emit: (args: string[]) => string
14
+ }
15
+
16
+ /**
17
+ * Mojo adapter's IRNode render context. Mojo's lowering currently
18
+ * doesn't consume any render-position flags (`isRootOfClientComponent`
19
+ * is handled differently here than in Hono/Go), so the Ctx is empty.
20
+ * Kept as a named alias so future flags can extend it without changing
21
+ * the `IRNodeEmitter` interface.
22
+ */
23
+ export type MojoRenderCtx = Record<string, never>
24
+
25
+ export interface MojoAdapterOptions {
26
+ /** Base path for client JS files (default: '/static/components/') */
27
+ clientJsBasePath?: string
28
+
29
+ /** Path to barefoot.js runtime (default: '/static/components/barefoot.js') */
30
+ barefootJsPath?: string
31
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * In-template memo / context seeding for the Mojolicious EP template adapter.
3
+ *
4
+ * Extracted from `mojo-adapter.ts` (domain-module refactor, issue #2018
5
+ * track D). Free functions taking a `MojoMemoContext` (built by the adapter's
6
+ * `memoCtx` getter) so the cluster depends only on the recursive expression entry, not
7
+ * the whole adapter class. These emit the `% my $x = ...;` seed lines that let
8
+ * the template body's bare `$x` resolve to a derived signal/memo value or an
9
+ * active context value at SSR time. Mirror of the Go adapter's `memo/*`.
10
+ */
11
+
12
+ import {
13
+ type ComponentIR,
14
+ type ContextConsumer,
15
+ collectContextConsumers,
16
+ computeSsrSeedPlan,
17
+ } from '@barefootjs/jsx'
18
+
19
+ import type { MojoMemoContext } from '../emit-context.ts'
20
+
21
+ /** Perl literal for a context-consumer's `createContext` default. */
22
+ export function contextDefaultPerl(c: ContextConsumer): string {
23
+ const d = c.defaultValue
24
+ if (d === null || d === undefined) return 'undef'
25
+ if (typeof d === 'string') return `'${d.replace(/[\\']/g, m => `\\${m}`)}'`
26
+ if (typeof d === 'boolean') return d ? '1' : '0'
27
+ return String(d)
28
+ }
29
+
30
+ /**
31
+ * Emit one `% my $<local> = bf->use_context(...)` seed line per context
32
+ * consumer so the template body's bare `$<local>` resolves to the active
33
+ * provider value (or the `createContext` default). (#1297)
34
+ */
35
+ export function generateContextConsumerSeed(ir: ComponentIR): string {
36
+ const consumers = collectContextConsumers(ir.metadata)
37
+ if (consumers.length === 0) return ''
38
+ return (
39
+ consumers
40
+ .map(
41
+ c =>
42
+ `% my $${c.localName} = bf->use_context('${c.contextName}', ${contextDefaultPerl(c)});`,
43
+ )
44
+ .join('\n') + '\n'
45
+ )
46
+ }
47
+
48
+ /**
49
+ * Emit `% my $<name> = <perl>;` seed lines for every `derived` step of the
50
+ * backend-neutral SSR seed plan — the scope/availability/ordering analysis
51
+ * lives in `computeSsrSeedPlan` (packages/jsx/src/ssr-seed-plan.ts); this
52
+ * only lowers each step's expression to Perl and applies the two
53
+ * backend-specific emit guards: skip an empty lowering, and skip a lowering
54
+ * that references no `$var` at all (a constant init/body — e.g. a `derived`
55
+ * step with empty `frees` — keeps the existing static ssr-defaults seed
56
+ * instead). `env-reader` and `opaque` steps emit nothing (the runtime
57
+ * supplies the reader, or the adapter's ssr-defaults path already covers it).
58
+ * (#1297, #2075)
59
+ */
60
+ export function generateDerivedMemoSeed(ctx: MojoMemoContext, ir: ComponentIR): string {
61
+ // Package G attached this to metadata at compile time; the `??` fallback
62
+ // only covers hand-built metadata in older tests that predate the attached
63
+ // plan — same shared function, so there's no divergence from the compiler.
64
+ const plan = ir.metadata.ssrSeedPlan ?? computeSsrSeedPlan(ir.metadata)
65
+ const lines: string[] = []
66
+ for (const step of plan.steps) {
67
+ if (step.kind !== 'derived') continue
68
+ const perl = ctx.convertExpressionToPerl(step.expr, step.parsed)
69
+ if (perl === '' || !/\$[A-Za-z_]\w*/.test(perl)) continue
70
+ lines.push(`% my $${step.name} = ${perl};`)
71
+ }
72
+ return lines.length > 0 ? lines.join('\n') + '\n' : ''
73
+ }