@barefootjs/rust 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.
- package/dist/adapter/expr/array-method.d.ts.map +1 -1
- package/dist/adapter/expr/emitters.d.ts +2 -2
- package/dist/adapter/expr/emitters.d.ts.map +1 -1
- package/dist/adapter/index.js +118 -18
- package/dist/adapter/lib/constants.d.ts.map +1 -1
- package/dist/adapter/lib/static-value.d.ts +13 -0
- package/dist/adapter/lib/static-value.d.ts.map +1 -0
- package/dist/adapter/minijinja-adapter.d.ts +49 -0
- package/dist/adapter/minijinja-adapter.d.ts.map +1 -1
- package/dist/build.js +118 -18
- package/dist/conformance-pins.d.ts.map +1 -1
- package/dist/index.js +120 -35
- package/dist/render-divergences.d.ts.map +1 -1
- package/package.json +3 -3
- package/runtime/src/num.rs +30 -0
- package/runtime/src/runtime.rs +78 -10
- package/runtime/tests/helper_vectors.rs +6 -0
- package/runtime/tests/template_primitives.rs +42 -0
- package/src/__tests__/minijinja-adapter-unit.test.ts +140 -0
- package/src/adapter/expr/array-method.ts +19 -0
- package/src/adapter/expr/emitters.ts +2 -2
- package/src/adapter/lib/constants.ts +3 -0
- package/src/adapter/lib/static-value.ts +39 -0
- package/src/adapter/minijinja-adapter.ts +170 -13
- package/src/conformance-pins.ts +24 -30
- package/src/render-divergences.ts +4 -16
- package/src/test-render.ts +11 -144
package/src/conformance-pins.ts
CHANGED
|
@@ -12,15 +12,17 @@
|
|
|
12
12
|
import type { ConformancePins } from '@barefootjs/jsx'
|
|
13
13
|
|
|
14
14
|
export const conformancePins: ConformancePins = {
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
// `todo-app` / `todo-app-ssr` no longer pinned (#2205) — the conformance
|
|
16
|
+
// harness now passes `siblingTemplatesRegistered: true` for fixtures with
|
|
17
|
+
// sibling `components`, matching `bf build`'s real semantics, so the
|
|
18
|
+
// BF103 loop-body cross-template check no longer fires spuriously. (Both
|
|
19
|
+
// fixtures are still skipped on this adapter via `render-divergences.ts`
|
|
20
|
+
// — #2209 — for an unrelated signal-seeding gap.)
|
|
21
|
+
// `static-array-children` no longer pinned (#2208) — `items`'s
|
|
22
|
+
// array-literal initializer is now recognized as fully-static
|
|
23
|
+
// (`resolveStaticLoopSource`) and inlined as a native MiniJinja
|
|
24
|
+
// list/dict literal in the `{% for %}` header, the same way a
|
|
25
|
+
// module-scope const's value is already seeded.
|
|
24
26
|
// The `([emoji, users]) => ...` / `([id, t]) => ...` params in these two
|
|
25
27
|
// fixtures no longer trip BF104 — the destructure itself now lowers
|
|
26
28
|
// cleanly to a native `{% set %}` accessor (#2087 Phase B). But both
|
|
@@ -44,10 +46,10 @@ export const conformancePins: ConformancePins = {
|
|
|
44
46
|
'static-array-from-props': [
|
|
45
47
|
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2087' },
|
|
46
48
|
],
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
+
// BF101 (unresolvable computed loop array, see above) fires; BF103
|
|
50
|
+
// (imported child in the loop body) no longer does now that the
|
|
51
|
+
// conformance harness passes `siblingTemplatesRegistered: true` (#2205).
|
|
49
52
|
'static-array-from-props-with-component': [
|
|
50
|
-
{ code: 'BF103', severity: 'error' },
|
|
51
53
|
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2087' },
|
|
52
54
|
],
|
|
53
55
|
// Rest-destructure `.map()` callbacks (#2087 Phase B): every shape now
|
|
@@ -91,22 +93,14 @@ export const conformancePins: ConformancePins = {
|
|
|
91
93
|
// / etc. via the same evaluator-JSON mechanism as `.filter` / `.every` /
|
|
92
94
|
// `.some`, so they render. Only the NESTED-in-a-predicate form above is
|
|
93
95
|
// refused (#2038).
|
|
94
|
-
//
|
|
95
|
-
// callback
|
|
96
|
-
// (`
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
// the shape loudly instead of emitting entity-escaped markup that
|
|
105
|
-
// silently renders tags as text.
|
|
106
|
-
'dangerous-inner-html': [{ code: 'BF101', severity: 'error' }],
|
|
107
|
-
// Edge-case sweep (Priority 12): `.replaceAll` has no lowering yet —
|
|
108
|
-
// only first-occurrence `.replace` is wired to the runtime helpers.
|
|
109
|
-
// Refused with BF101 rather than reusing the first-only lowering,
|
|
110
|
-
// which would silently change semantics.
|
|
111
|
-
'string-replaceall': [{ code: 'BF101', severity: 'error' }],
|
|
96
|
+
// `array-map-function-reference` no longer pinned — a bare-identifier
|
|
97
|
+
// `.map(format)` callback now resolves one hop to its declaration
|
|
98
|
+
// (`resolveCallbackMethodFunctionReferences`, #2206), the same mechanism
|
|
99
|
+
// #2090 established for `.sort(fnref)`.
|
|
100
|
+
// `dangerous-inner-html` no longer pinned — a compile-time string-literal
|
|
101
|
+
// `dangerouslySetInnerHTML={{ __html: '...' }}` is spliced directly into
|
|
102
|
+
// the template as trusted raw text (`resolveDangerousInnerHtml`, #2207).
|
|
103
|
+
// A dynamic/signal-derived value still refuses with BF101 — see the
|
|
104
|
+
// `dangerous-inner-html-dynamic` fixture/pin below (tracked: #2215).
|
|
105
|
+
'dangerous-inner-html-dynamic': [{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2215' }],
|
|
112
106
|
}
|
|
@@ -17,20 +17,8 @@
|
|
|
17
17
|
import type { RenderDivergences } from '@barefootjs/jsx'
|
|
18
18
|
|
|
19
19
|
export const renderDivergences: RenderDivergences = {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
'static-attr-escape':
|
|
25
|
-
'static attribute values are not HTML-escaped (`title="Fish & Chips"` emitted raw; Hono escapes)',
|
|
26
|
-
'object-entries-map':
|
|
27
|
-
'`Object.entries(prop).map(([k, v]) => …)` renders an EMPTY list — the object-shaped prop silently produces zero iterations',
|
|
28
|
-
'nested-loop-outer-binding':
|
|
29
|
-
'nested-loop inner items carry `data-key` where the reference emits the depth-suffixed `data-key-1`',
|
|
30
|
-
'jsx-element-prop':
|
|
31
|
-
'a JSX element passed as a NON-children prop renders an empty slot — the element value is silently dropped',
|
|
32
|
-
'string-slice':
|
|
33
|
-
'`.slice()` on a STRING renders empty (array-slice helper misfires on strings)',
|
|
34
|
-
'string-trim-sided':
|
|
35
|
-
'`.trimStart()` / `.trimEnd()` render empty (no lowering)',
|
|
20
|
+
// `todo-app` / `todo-app-ssr` no longer diverge (#2209) — the shared
|
|
21
|
+
// `evaluateSignalInit` (`@barefootjs/jsx`, sandboxed real-JS evaluation
|
|
22
|
+
// instead of a fixed regex-shape catalogue) now correctly seeds `todos`
|
|
23
|
+
// from `(props.initialTodos ?? []).map(t => ({ ...t, editing: false }))`.
|
|
36
24
|
}
|
package/src/test-render.ts
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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`,
|