@barefootjs/xslate 0.18.5 → 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.
- package/dist/adapter/index.js +82 -5
- package/dist/adapter/lib/static-value.d.ts +18 -0
- package/dist/adapter/lib/static-value.d.ts.map +1 -0
- package/dist/adapter/props/prop-classes.d.ts +25 -7
- package/dist/adapter/props/prop-classes.d.ts.map +1 -1
- package/dist/adapter/xslate-adapter.d.ts +41 -0
- package/dist/adapter/xslate-adapter.d.ts.map +1 -1
- package/dist/build.js +82 -5
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +84 -14
- package/dist/render-divergences.d.ts.map +1 -1
- package/lib/BarefootJS/Backend/Xslate.pm +1 -1
- package/package.json +3 -3
- package/src/__tests__/xslate-adapter.test.ts +119 -0
- package/src/adapter/lib/static-value.ts +44 -0
- package/src/adapter/props/prop-classes.ts +29 -7
- package/src/adapter/xslate-adapter.ts +102 -4
- package/src/conformance-pins.ts +26 -28
- package/src/render-divergences.ts +6 -1
- package/src/test-render.ts +13 -139
package/src/conformance-pins.ts
CHANGED
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
import type { ConformancePins } from '@barefootjs/jsx'
|
|
10
10
|
|
|
11
11
|
export const conformancePins: ConformancePins = {
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
'
|
|
12
|
+
// `todo-app` / `todo-app-ssr` no longer pinned (#2205) — the conformance
|
|
13
|
+
// harness now passes `siblingTemplatesRegistered: true` for fixtures with
|
|
14
|
+
// sibling `components`, matching `bf build`'s real semantics, so the
|
|
15
|
+
// BF103 loop-body cross-template check no longer fires spuriously. (Both
|
|
16
|
+
// fixtures are still skipped on this adapter via `render-divergences.ts`
|
|
17
|
+
// — #2209 — for an unrelated signal-seeding gap.)
|
|
18
|
+
// `static-array-children` no longer pinned (#2208) — `items`'s
|
|
19
|
+
// array-literal initializer is now recognized as fully-static
|
|
20
|
+
// (`resolveStaticLoopSource`) and inlined as a native Kolon array/hash
|
|
21
|
+
// literal in the `for EXPR -> $item` header, the same way a module-scope
|
|
22
|
+
// const's value is already seeded.
|
|
23
23
|
// `([emoji, users]) => ...` / `([id, t]) => ...` are plain array-index
|
|
24
24
|
// (tuple) destructures, no rest — #2087 Phase B's `segments`-walking
|
|
25
25
|
// accessor lowers both to `$__bf_item[0]` / `$__bf_item[1]` `: my` locals
|
|
@@ -32,12 +32,11 @@ export const conformancePins: ConformancePins = {
|
|
|
32
32
|
// simply unreachable before because BF104 refused the destructure shape
|
|
33
33
|
// first.
|
|
34
34
|
'static-array-from-props': [{ code: 'BF101', severity: 'error' }],
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
],
|
|
35
|
+
// The BF101 above fires; BF104 no longer does (see above), and BF103
|
|
36
|
+
// (sibling-imported `<Tag>` child component in the loop body) no longer
|
|
37
|
+
// does either now that the conformance harness passes
|
|
38
|
+
// `siblingTemplatesRegistered: true` (#2205).
|
|
39
|
+
'static-array-from-props-with-component': [{ code: 'BF101', severity: 'error' }],
|
|
41
40
|
// #1310 / #2087: rest destructure in .map() callback. All four shapes now
|
|
42
41
|
// lower via #2087 Phase B's `segments`-walking accessor:
|
|
43
42
|
// - object-rest read via member access (`rest-destructure-object-in-map`):
|
|
@@ -104,15 +103,14 @@ export const conformancePins: ConformancePins = {
|
|
|
104
103
|
// `find_last_index` via the same Kolon-lambda mechanism as `.filter` /
|
|
105
104
|
// `.every` / `.some`, so they render. Only the NESTED-in-a-predicate form
|
|
106
105
|
// above is refused (#2038).
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
'dangerous-inner-html': [{ code: 'BF101', severity: 'error' }],
|
|
106
|
+
// `array-map-function-reference` no longer pinned — a bare-identifier
|
|
107
|
+
// `.map(format)` callback now resolves one hop to its declaration
|
|
108
|
+
// (`resolveCallbackMethodFunctionReferences`, #2206), the same mechanism
|
|
109
|
+
// #2090 established for `.sort(fnref)`.
|
|
110
|
+
// `dangerous-inner-html` no longer pinned — a compile-time string-literal
|
|
111
|
+
// `dangerouslySetInnerHTML={{ __html: '...' }}` is spliced directly into
|
|
112
|
+
// the template as trusted raw text (`resolveDangerousInnerHtml`, #2207).
|
|
113
|
+
// A dynamic/signal-derived value still refuses with BF101 — see the
|
|
114
|
+
// `dangerous-inner-html-dynamic` fixture/pin below (tracked: #2215).
|
|
115
|
+
'dangerous-inner-html-dynamic': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2215' }],
|
|
118
116
|
}
|
|
@@ -14,4 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
import type { RenderDivergences } from '@barefootjs/jsx'
|
|
16
16
|
|
|
17
|
-
export const renderDivergences: RenderDivergences = {
|
|
17
|
+
export const renderDivergences: RenderDivergences = {
|
|
18
|
+
// `todo-app` / `todo-app-ssr` no longer diverge (#2209) — the shared
|
|
19
|
+
// `evaluateSignalInit` (`@barefootjs/jsx`, sandboxed real-JS evaluation
|
|
20
|
+
// instead of a fixed regex-shape catalogue) now correctly seeds `todos`
|
|
21
|
+
// from `(props.initialTodos ?? []).map(t => ({ ...t, editing: false }))`.
|
|
22
|
+
}
|
package/src/test-render.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
471
|
-
if (
|
|
472
|
-
entries.push(`${param.name} => ${
|
|
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
|
-
}
|