@barefootjs/rust 0.18.5 → 0.19.0

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.
@@ -28,7 +28,7 @@
28
28
  * that closes that gap.
29
29
  */
30
30
 
31
- import { compileJSX, extractSsrDefaults, importsSearchParams } from '@barefootjs/jsx'
31
+ import { compileJSX, extractSsrDefaults, importsSearchParams, evaluateSignalInit } from '@barefootjs/jsx'
32
32
  import type { ComponentIR } from '@barefootjs/jsx'
33
33
  import { mkdir, rm } from 'node:fs/promises'
34
34
  import { resolve } from 'node:path'
@@ -170,8 +170,15 @@ export async function renderMinijinjaComponent(options: RenderOptions): Promise<
170
170
  }
171
171
  }
172
172
 
173
- // Compile parent source.
174
- const result = compileJSX(source, 'component.tsx', { adapter, outputIR: true })
173
+ // Compile parent source. `siblingTemplatesRegistered: Boolean(components)`
174
+ // matches this harness's real behavior every sibling child template is registered
175
+ // alongside the parent before rendering, so a loop-body cross-template
176
+ // call resolves at render time (#2205).
177
+ const result = compileJSX(source, 'component.tsx', {
178
+ adapter,
179
+ outputIR: true,
180
+ siblingTemplatesRegistered: Boolean(components),
181
+ })
175
182
 
176
183
  const errors = result.errors.filter(e => e.severity === 'error')
177
184
  if (errors.length > 0) {
@@ -441,7 +448,7 @@ function buildVars(
441
448
  for (const param of ir.metadata.propsParams) {
442
449
  if (props && param.name in props) continue
443
450
  if (param.defaultValue) {
444
- const value = jsDefaultToVarValue(param.defaultValue)
451
+ const value = evaluateSignalInit(param.defaultValue.trim(), props)
445
452
  if (value !== null) {
446
453
  vars[param.name] = value
447
454
  continue
@@ -510,146 +517,6 @@ function buildVars(
510
517
  return vars
511
518
  }
512
519
 
513
- /**
514
- * Convert a destructure-default's JS source text (`{ size = 'md' }`'s
515
- * `'md'`) to a real JS value. Near-verbatim port of `buildPythonProps`'s
516
- * `jsToPyValue` helper — which returned Python SOURCE text (safe to reuse
517
- * verbatim for a string/numeric literal, since JS and Python share that
518
- * literal grammar) — ported to resolve directly to the JS runtime value
519
- * instead, via the shared `parseLiteral` for the literal shapes both
520
- * versions handle identically. The match ORDER is preserved from
521
- * `jsToPyValue`: numeric/string/bool/`[]` are checked BEFORE the `??`
522
- * regex, so a string literal containing a literal `??` substring (e.g.
523
- * `'a??b'`) is caught by the string-literal branch first, not
524
- * mis-parsed as a nullish-coalescing default.
525
- */
526
- function jsDefaultToVarValue(jsValue: string): unknown {
527
- const v = jsValue.trim()
528
- if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v)
529
- const strMatch = v.match(/^(['"])(.*)\1$/s)
530
- if (strMatch) return unescapeJsString(strMatch[2])
531
- if (v === 'true') return true
532
- if (v === 'false') return false
533
- if (v === '[]') return []
534
- const nullishMatch = v.match(/\?\?\s*(.+)$/)
535
- if (nullishMatch) return jsDefaultToVarValue(nullishMatch[1])
536
- if (v.startsWith('props.')) return null
537
- return null
538
- }
539
-
540
- /**
541
- * Evaluate a signal initializer expression using provided props.
542
- * Handles: props.initial ?? 0, props.value, literal values.
543
- */
544
- export function evaluateSignalInit(
545
- expr: string,
546
- props?: Record<string, unknown>,
547
- ): unknown {
548
- const nullishMatch = expr.match(/^props\.(\w+)\s*\?\?\s*(.+)$/)
549
- if (nullishMatch) {
550
- const propName = nullishMatch[1]
551
- const defaultExpr = nullishMatch[2].trim()
552
- if (props && propName in props) return props[propName]
553
- return parseLiteral(defaultExpr)
554
- }
555
-
556
- const propsMatch = expr.match(/^props\.(\w+)$/)
557
- if (propsMatch) {
558
- if (props && propsMatch[1] in props) return props[propsMatch[1]]
559
- return null
560
- }
561
-
562
- return parseLiteral(expr)
563
- }
564
-
565
- function parseLiteral(expr: string): unknown {
566
- if (/^-?\d+(\.\d+)?$/.test(expr)) return Number(expr)
567
- if (expr === 'true') return true
568
- if (expr === 'false') return false
569
- if (expr === '[]') return []
570
-
571
- {
572
- const t = expr.trim()
573
- if (t.startsWith('[') && t.endsWith(']')) {
574
- const inner = t.slice(1, -1).trim()
575
- if (!inner) return []
576
- const out: unknown[] = []
577
- for (const seg of splitTopLevelCommas(inner)) {
578
- if (!seg.trim()) continue
579
- const parsed = parseLiteral(seg.trim())
580
- if (parsed === null && seg.trim() !== 'null') return null
581
- out.push(parsed)
582
- }
583
- return out
584
- }
585
- }
586
-
587
- const stringMatch = expr.match(/^(['"])(.*)\1$/s)
588
- if (stringMatch) return unescapeJsString(stringMatch[2])
589
-
590
- const trimmed = expr.trim()
591
- if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
592
- const inner = trimmed.slice(1, -1).trim()
593
- if (!inner) return {}
594
- const obj: Record<string, unknown> = {}
595
- for (const pair of splitTopLevelCommas(inner)) {
596
- if (!pair.trim()) continue
597
- const colonIdx = pair.indexOf(':')
598
- if (colonIdx < 0) return null
599
- let key = pair.slice(0, colonIdx).trim()
600
- const val = pair.slice(colonIdx + 1).trim()
601
- const keyMatch = key.match(/^(['"])(.*)\1$/s)
602
- if (keyMatch) key = unescapeJsString(keyMatch[2])
603
- const parsedVal = parseLiteral(val)
604
- if (parsedVal === null && val !== 'null') return null
605
- obj[key] = parsedVal
606
- }
607
- return obj
608
- }
609
- return null
610
- }
611
-
612
- function splitTopLevelCommas(inner: string): string[] {
613
- const segments: string[] = []
614
- let depth = 0
615
- let start = 0
616
- let quote: string | null = null
617
- for (let i = 0; i < inner.length; i++) {
618
- const c = inner[i]
619
- if (quote) {
620
- if (c === quote) {
621
- let backslashes = 0
622
- for (let j = i - 1; j >= 0 && inner[j] === '\\'; j--) backslashes++
623
- if (backslashes % 2 === 0) quote = null
624
- }
625
- continue
626
- }
627
- if (c === '"' || c === "'") {
628
- quote = c
629
- continue
630
- }
631
- if (c === '{' || c === '[') depth++
632
- else if (c === '}' || c === ']') depth--
633
- else if (c === ',' && depth === 0) {
634
- segments.push(inner.slice(start, i))
635
- start = i + 1
636
- }
637
- }
638
- segments.push(inner.slice(start))
639
- return segments
640
- }
641
-
642
- function unescapeJsString(s: string): string {
643
- return s.replace(/\\(.)/g, (_, c) => {
644
- switch (c) {
645
- case 'n': return '\n'
646
- case 'r': return '\r'
647
- case 't': return '\t'
648
- case '0': return '\0'
649
- default: return c
650
- }
651
- })
652
- }
653
520
 
654
521
  /**
655
522
  * Recursively replace JS's non-finite numbers (`NaN`, `Infinity`,