@barefootjs/xslate 0.18.4 → 0.18.7

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.
@@ -17,7 +17,7 @@
17
17
  * than the Mojo harness's literal `test_<sN>`.
18
18
  */
19
19
 
20
- import { compileJSX, extractSsrDefaults, importsSearchParams } from '@barefootjs/jsx'
20
+ import { compileJSX, extractSsrDefaults, importsSearchParams, evaluateSignalInit, tryEvaluateSignalInit } from '@barefootjs/jsx'
21
21
  import type { ComponentIR } from '@barefootjs/jsx'
22
22
  import { mkdir, rm } from 'node:fs/promises'
23
23
  import { resolve } from 'node:path'
@@ -135,8 +135,15 @@ export async function renderXslateComponent(options: RenderOptions): Promise<str
135
135
  }
136
136
  }
137
137
 
138
- // Compile parent source.
139
- const result = compileJSX(source, 'component.tsx', { adapter, outputIR: true })
138
+ // Compile parent source. `siblingTemplatesRegistered: Boolean(components)`
139
+ // matches this harness's real behavior every sibling child template is registered
140
+ // alongside the parent before rendering, so a loop-body cross-template
141
+ // call resolves at render time (#2205).
142
+ const result = compileJSX(source, 'component.tsx', {
143
+ adapter,
144
+ outputIR: true,
145
+ siblingTemplatesRegistered: Boolean(components),
146
+ })
140
147
 
141
148
  const errors = result.errors.filter(e => e.severity === 'error')
142
149
  if (errors.length > 0) {
@@ -467,9 +474,9 @@ function buildPerlProps(
467
474
  for (const param of ir.metadata.propsParams) {
468
475
  if (props && param.name in props) continue
469
476
  if (param.defaultValue) {
470
- const perlValue = jsToPerlValue(param.defaultValue)
471
- if (perlValue !== null) {
472
- entries.push(`${param.name} => ${perlValue}`)
477
+ const result = tryEvaluateSignalInit(param.defaultValue.trim(), props)
478
+ if (result.ok) {
479
+ entries.push(`${param.name} => ${toPerlLiteral(result.value)}`)
473
480
  continue
474
481
  }
475
482
  }
@@ -547,119 +554,6 @@ function buildPerlProps(
547
554
  return `{${entries.join(', ')}}`
548
555
  }
549
556
 
550
- /**
551
- * Evaluate a signal initializer expression using provided props.
552
- * Handles: props.initial ?? 0, props.value, literal values.
553
- */
554
- export function evaluateSignalInit(
555
- expr: string,
556
- props?: Record<string, unknown>,
557
- ): unknown {
558
- const nullishMatch = expr.match(/^props\.(\w+)\s*\?\?\s*(.+)$/)
559
- if (nullishMatch) {
560
- const propName = nullishMatch[1]
561
- const defaultExpr = nullishMatch[2].trim()
562
- if (props && propName in props) return props[propName]
563
- return parseLiteral(defaultExpr)
564
- }
565
-
566
- const propsMatch = expr.match(/^props\.(\w+)$/)
567
- if (propsMatch) {
568
- if (props && propsMatch[1] in props) return props[propsMatch[1]]
569
- return null
570
- }
571
-
572
- return parseLiteral(expr)
573
- }
574
-
575
- function parseLiteral(expr: string): unknown {
576
- if (/^-?\d+(\.\d+)?$/.test(expr)) return Number(expr)
577
- if (expr === 'true') return true
578
- if (expr === 'false') return false
579
- if (expr === '[]') return []
580
-
581
- {
582
- const t = expr.trim()
583
- if (t.startsWith('[') && t.endsWith(']')) {
584
- const inner = t.slice(1, -1).trim()
585
- if (!inner) return []
586
- const out: unknown[] = []
587
- for (const seg of splitTopLevelCommas(inner)) {
588
- if (!seg.trim()) continue
589
- const parsed = parseLiteral(seg.trim())
590
- if (parsed === null && seg.trim() !== 'null') return null
591
- out.push(parsed)
592
- }
593
- return out
594
- }
595
- }
596
-
597
- const stringMatch = expr.match(/^(['"])(.*)\1$/s)
598
- if (stringMatch) return unescapeJsString(stringMatch[2])
599
-
600
- const trimmed = expr.trim()
601
- if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
602
- const inner = trimmed.slice(1, -1).trim()
603
- if (!inner) return {}
604
- const obj: Record<string, unknown> = {}
605
- for (const pair of splitTopLevelCommas(inner)) {
606
- if (!pair.trim()) continue
607
- const colonIdx = pair.indexOf(':')
608
- if (colonIdx < 0) return null
609
- let key = pair.slice(0, colonIdx).trim()
610
- const val = pair.slice(colonIdx + 1).trim()
611
- const keyMatch = key.match(/^(['"])(.*)\1$/s)
612
- if (keyMatch) key = unescapeJsString(keyMatch[2])
613
- const parsedVal = parseLiteral(val)
614
- if (parsedVal === null && val !== 'null') return null
615
- obj[key] = parsedVal
616
- }
617
- return obj
618
- }
619
- return null
620
- }
621
-
622
- function splitTopLevelCommas(inner: string): string[] {
623
- const segments: string[] = []
624
- let depth = 0
625
- let start = 0
626
- let quote: string | null = null
627
- for (let i = 0; i < inner.length; i++) {
628
- const c = inner[i]
629
- if (quote) {
630
- if (c === quote) {
631
- let backslashes = 0
632
- for (let j = i - 1; j >= 0 && inner[j] === '\\'; j--) backslashes++
633
- if (backslashes % 2 === 0) quote = null
634
- }
635
- continue
636
- }
637
- if (c === '"' || c === "'") {
638
- quote = c
639
- continue
640
- }
641
- if (c === '{' || c === '[') depth++
642
- else if (c === '}' || c === ']') depth--
643
- else if (c === ',' && depth === 0) {
644
- segments.push(inner.slice(start, i))
645
- start = i + 1
646
- }
647
- }
648
- segments.push(inner.slice(start))
649
- return segments
650
- }
651
-
652
- function unescapeJsString(s: string): string {
653
- return s.replace(/\\(.)/g, (_, c) => {
654
- switch (c) {
655
- case 'n': return '\n'
656
- case 'r': return '\r'
657
- case 't': return '\t'
658
- case '0': return '\0'
659
- default: return c
660
- }
661
- })
662
- }
663
557
 
664
558
  /** Perl single-quoted string escape: `'` AND `\` need escaping. */
665
559
  function perlSingleQuote(s: string): string {
@@ -685,23 +579,3 @@ function toPerlLiteral(value: unknown): string {
685
579
  return 'undef'
686
580
  }
687
581
 
688
- /**
689
- * Convert a JS literal value to a Perl literal.
690
- * Handles: numbers, strings, booleans, empty arrays, props.xxx ?? default.
691
- */
692
- function jsToPerlValue(jsValue: string): string | null {
693
- const v = jsValue.trim()
694
-
695
- if (/^-?\d+(\.\d+)?$/.test(v)) return v
696
- if (/^['"].*['"]$/.test(v)) return v
697
- if (v === 'true') return '1'
698
- if (v === 'false') return '0'
699
- if (v === '[]') return '[]'
700
-
701
- const nullishMatch = v.match(/\?\?\s*(.+)$/)
702
- if (nullishMatch) return jsToPerlValue(nullishMatch[1])
703
-
704
- if (v.startsWith('props.')) return 'undef'
705
-
706
- return null
707
- }