@barefootjs/jinja 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.
- package/dist/adapter/index.js +74 -6
- package/dist/adapter/jinja-adapter.d.ts +41 -0
- package/dist/adapter/jinja-adapter.d.ts.map +1 -1
- package/dist/adapter/lib/static-value.d.ts +15 -0
- package/dist/adapter/lib/static-value.d.ts.map +1 -0
- package/dist/adapter/props/prop-classes.d.ts +3 -2
- package/dist/adapter/props/prop-classes.d.ts.map +1 -1
- package/dist/build.js +74 -6
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +75 -12
- package/dist/render-divergences.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/jinja-adapter-unit.test.ts +120 -0
- package/src/__tests__/jinja-adapter.test.ts +13 -0
- package/src/adapter/jinja-adapter.ts +102 -5
- package/src/adapter/lib/static-value.ts +41 -0
- package/src/adapter/props/prop-classes.ts +4 -3
- package/src/conformance-pins.ts +24 -25
- package/src/render-divergences.ts +6 -1
- package/src/test-render.ts +13 -142
package/src/test-render.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* generated render script (Python, not Perl) and its literal syntax differ.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { compileJSX, extractSsrDefaults, importsSearchParams } from '@barefootjs/jsx'
|
|
19
|
+
import { compileJSX, extractSsrDefaults, importsSearchParams, evaluateSignalInit, tryEvaluateSignalInit } from '@barefootjs/jsx'
|
|
20
20
|
import type { ComponentIR } from '@barefootjs/jsx'
|
|
21
21
|
import { mkdir, rm } from 'node:fs/promises'
|
|
22
22
|
import { resolve } from 'node:path'
|
|
@@ -125,8 +125,15 @@ export async function renderJinjaComponent(options: RenderOptions): Promise<stri
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
// Compile parent source.
|
|
129
|
-
|
|
128
|
+
// Compile parent source. `siblingTemplatesRegistered: Boolean(components)`
|
|
129
|
+
// matches this harness's real behavior — every sibling child template is registered
|
|
130
|
+
// alongside the parent before rendering, so a loop-body cross-template
|
|
131
|
+
// call resolves at render time (#2205).
|
|
132
|
+
const result = compileJSX(source, 'component.tsx', {
|
|
133
|
+
adapter,
|
|
134
|
+
outputIR: true,
|
|
135
|
+
siblingTemplatesRegistered: Boolean(components),
|
|
136
|
+
})
|
|
130
137
|
|
|
131
138
|
const errors = result.errors.filter(e => e.severity === 'error')
|
|
132
139
|
if (errors.length > 0) {
|
|
@@ -461,9 +468,9 @@ function buildPythonProps(
|
|
|
461
468
|
for (const param of ir.metadata.propsParams) {
|
|
462
469
|
if (props && param.name in props) continue
|
|
463
470
|
if (param.defaultValue) {
|
|
464
|
-
const
|
|
465
|
-
if (
|
|
466
|
-
entries.push(`${pyStr(param.name)}: ${
|
|
471
|
+
const result = tryEvaluateSignalInit(param.defaultValue.trim(), props)
|
|
472
|
+
if (result.ok) {
|
|
473
|
+
entries.push(`${pyStr(param.name)}: ${toPyLiteral(result.value)}`)
|
|
467
474
|
continue
|
|
468
475
|
}
|
|
469
476
|
}
|
|
@@ -539,119 +546,6 @@ function buildPythonProps(
|
|
|
539
546
|
return `{${entries.join(', ')}}`
|
|
540
547
|
}
|
|
541
548
|
|
|
542
|
-
/**
|
|
543
|
-
* Evaluate a signal initializer expression using provided props.
|
|
544
|
-
* Handles: props.initial ?? 0, props.value, literal values.
|
|
545
|
-
*/
|
|
546
|
-
export function evaluateSignalInit(
|
|
547
|
-
expr: string,
|
|
548
|
-
props?: Record<string, unknown>,
|
|
549
|
-
): unknown {
|
|
550
|
-
const nullishMatch = expr.match(/^props\.(\w+)\s*\?\?\s*(.+)$/)
|
|
551
|
-
if (nullishMatch) {
|
|
552
|
-
const propName = nullishMatch[1]
|
|
553
|
-
const defaultExpr = nullishMatch[2].trim()
|
|
554
|
-
if (props && propName in props) return props[propName]
|
|
555
|
-
return parseLiteral(defaultExpr)
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
const propsMatch = expr.match(/^props\.(\w+)$/)
|
|
559
|
-
if (propsMatch) {
|
|
560
|
-
if (props && propsMatch[1] in props) return props[propsMatch[1]]
|
|
561
|
-
return null
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
return parseLiteral(expr)
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function parseLiteral(expr: string): unknown {
|
|
568
|
-
if (/^-?\d+(\.\d+)?$/.test(expr)) return Number(expr)
|
|
569
|
-
if (expr === 'true') return true
|
|
570
|
-
if (expr === 'false') return false
|
|
571
|
-
if (expr === '[]') return []
|
|
572
|
-
|
|
573
|
-
{
|
|
574
|
-
const t = expr.trim()
|
|
575
|
-
if (t.startsWith('[') && t.endsWith(']')) {
|
|
576
|
-
const inner = t.slice(1, -1).trim()
|
|
577
|
-
if (!inner) return []
|
|
578
|
-
const out: unknown[] = []
|
|
579
|
-
for (const seg of splitTopLevelCommas(inner)) {
|
|
580
|
-
if (!seg.trim()) continue
|
|
581
|
-
const parsed = parseLiteral(seg.trim())
|
|
582
|
-
if (parsed === null && seg.trim() !== 'null') return null
|
|
583
|
-
out.push(parsed)
|
|
584
|
-
}
|
|
585
|
-
return out
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const stringMatch = expr.match(/^(['"])(.*)\1$/s)
|
|
590
|
-
if (stringMatch) return unescapeJsString(stringMatch[2])
|
|
591
|
-
|
|
592
|
-
const trimmed = expr.trim()
|
|
593
|
-
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
|
|
594
|
-
const inner = trimmed.slice(1, -1).trim()
|
|
595
|
-
if (!inner) return {}
|
|
596
|
-
const obj: Record<string, unknown> = {}
|
|
597
|
-
for (const pair of splitTopLevelCommas(inner)) {
|
|
598
|
-
if (!pair.trim()) continue
|
|
599
|
-
const colonIdx = pair.indexOf(':')
|
|
600
|
-
if (colonIdx < 0) return null
|
|
601
|
-
let key = pair.slice(0, colonIdx).trim()
|
|
602
|
-
const val = pair.slice(colonIdx + 1).trim()
|
|
603
|
-
const keyMatch = key.match(/^(['"])(.*)\1$/s)
|
|
604
|
-
if (keyMatch) key = unescapeJsString(keyMatch[2])
|
|
605
|
-
const parsedVal = parseLiteral(val)
|
|
606
|
-
if (parsedVal === null && val !== 'null') return null
|
|
607
|
-
obj[key] = parsedVal
|
|
608
|
-
}
|
|
609
|
-
return obj
|
|
610
|
-
}
|
|
611
|
-
return null
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
function splitTopLevelCommas(inner: string): string[] {
|
|
615
|
-
const segments: string[] = []
|
|
616
|
-
let depth = 0
|
|
617
|
-
let start = 0
|
|
618
|
-
let quote: string | null = null
|
|
619
|
-
for (let i = 0; i < inner.length; i++) {
|
|
620
|
-
const c = inner[i]
|
|
621
|
-
if (quote) {
|
|
622
|
-
if (c === quote) {
|
|
623
|
-
let backslashes = 0
|
|
624
|
-
for (let j = i - 1; j >= 0 && inner[j] === '\\'; j--) backslashes++
|
|
625
|
-
if (backslashes % 2 === 0) quote = null
|
|
626
|
-
}
|
|
627
|
-
continue
|
|
628
|
-
}
|
|
629
|
-
if (c === '"' || c === "'") {
|
|
630
|
-
quote = c
|
|
631
|
-
continue
|
|
632
|
-
}
|
|
633
|
-
if (c === '{' || c === '[') depth++
|
|
634
|
-
else if (c === '}' || c === ']') depth--
|
|
635
|
-
else if (c === ',' && depth === 0) {
|
|
636
|
-
segments.push(inner.slice(start, i))
|
|
637
|
-
start = i + 1
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
segments.push(inner.slice(start))
|
|
641
|
-
return segments
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function unescapeJsString(s: string): string {
|
|
645
|
-
return s.replace(/\\(.)/g, (_, c) => {
|
|
646
|
-
switch (c) {
|
|
647
|
-
case 'n': return '\n'
|
|
648
|
-
case 'r': return '\r'
|
|
649
|
-
case 't': return '\t'
|
|
650
|
-
case '0': return '\0'
|
|
651
|
-
default: return c
|
|
652
|
-
}
|
|
653
|
-
})
|
|
654
|
-
}
|
|
655
549
|
|
|
656
550
|
/**
|
|
657
551
|
* Python string literal for arbitrary text, via `JSON.stringify`. JSON's
|
|
@@ -689,26 +583,3 @@ function toPyLiteral(value: unknown): string {
|
|
|
689
583
|
return 'None'
|
|
690
584
|
}
|
|
691
585
|
|
|
692
|
-
/**
|
|
693
|
-
* Convert a JS literal value to a Python literal.
|
|
694
|
-
* Handles: numbers, strings, booleans, empty arrays, props.xxx ?? default.
|
|
695
|
-
*/
|
|
696
|
-
function jsToPyValue(jsValue: string): string | null {
|
|
697
|
-
const v = jsValue.trim()
|
|
698
|
-
|
|
699
|
-
if (/^-?\d+(\.\d+)?$/.test(v)) return v
|
|
700
|
-
// A JS string literal (single- or double-quoted) is, character-for-character,
|
|
701
|
-
// ALSO a valid Python string literal for the common escape sequences both
|
|
702
|
-
// languages share — pass it through verbatim rather than re-quoting.
|
|
703
|
-
if (/^['"].*['"]$/.test(v)) return v
|
|
704
|
-
if (v === 'true') return 'True'
|
|
705
|
-
if (v === 'false') return 'False'
|
|
706
|
-
if (v === '[]') return '[]'
|
|
707
|
-
|
|
708
|
-
const nullishMatch = v.match(/\?\?\s*(.+)$/)
|
|
709
|
-
if (nullishMatch) return jsToPyValue(nullishMatch[1])
|
|
710
|
-
|
|
711
|
-
if (v.startsWith('props.')) return 'None'
|
|
712
|
-
|
|
713
|
-
return null
|
|
714
|
-
}
|