@barefootjs/rust 0.1.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/README.md +194 -0
- package/dist/adapter/analysis/component-tree.d.ts +26 -0
- package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
- package/dist/adapter/boolean-result.d.ts +85 -0
- package/dist/adapter/boolean-result.d.ts.map +1 -0
- package/dist/adapter/emit-context.d.ts +107 -0
- package/dist/adapter/emit-context.d.ts.map +1 -0
- package/dist/adapter/expr/array-method.d.ts +75 -0
- package/dist/adapter/expr/array-method.d.ts.map +1 -0
- package/dist/adapter/expr/emitters.d.ts +143 -0
- package/dist/adapter/expr/emitters.d.ts.map +1 -0
- package/dist/adapter/index.d.ts +6 -0
- package/dist/adapter/index.d.ts.map +1 -0
- package/dist/adapter/index.js +189091 -0
- package/dist/adapter/lib/constants.d.ts +25 -0
- package/dist/adapter/lib/constants.d.ts.map +1 -0
- package/dist/adapter/lib/ir-scope.d.ts +50 -0
- package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
- package/dist/adapter/lib/minijinja-naming.d.ts +64 -0
- package/dist/adapter/lib/minijinja-naming.d.ts.map +1 -0
- package/dist/adapter/lib/types.d.ts +32 -0
- package/dist/adapter/lib/types.d.ts.map +1 -0
- package/dist/adapter/memo/seed.d.ts +84 -0
- package/dist/adapter/memo/seed.d.ts.map +1 -0
- package/dist/adapter/minijinja-adapter.d.ts +421 -0
- package/dist/adapter/minijinja-adapter.d.ts.map +1 -0
- package/dist/adapter/props/prop-classes.d.ts +33 -0
- package/dist/adapter/props/prop-classes.d.ts.map +1 -0
- package/dist/adapter/spread/spread-codegen.d.ts +63 -0
- package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
- package/dist/adapter/value/parsed-literal.d.ts +28 -0
- package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
- package/dist/build.d.ts +29 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +189111 -0
- package/dist/conformance-pins.d.ts +13 -0
- package/dist/conformance-pins.d.ts.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +189112 -0
- package/package.json +67 -0
- package/runtime/Cargo.lock +124 -0
- package/runtime/Cargo.toml +21 -0
- package/runtime/src/backend_minijinja.rs +176 -0
- package/runtime/src/bin/bf-render.rs +147 -0
- package/runtime/src/evaluator.rs +770 -0
- package/runtime/src/lib.rs +19 -0
- package/runtime/src/manifest.rs +258 -0
- package/runtime/src/num.rs +558 -0
- package/runtime/src/runtime.rs +1548 -0
- package/runtime/src/search_params.rs +173 -0
- package/runtime/tests/eval_vectors.rs +94 -0
- package/runtime/tests/evaluator.rs +407 -0
- package/runtime/tests/helper_vectors.rs +348 -0
- package/runtime/tests/manifest.rs +169 -0
- package/runtime/tests/omit.rs +79 -0
- package/runtime/tests/props_attr.rs +75 -0
- package/runtime/tests/query.rs +50 -0
- package/runtime/tests/render_child.rs +210 -0
- package/runtime/tests/search_params.rs +68 -0
- package/runtime/tests/spread_attrs.rs +94 -0
- package/runtime/tests/template_primitives.rs +376 -0
- package/runtime/tests/vector-divergences.json +33 -0
- package/src/__tests__/minijinja-adapter-unit.test.ts +392 -0
- package/src/__tests__/minijinja-adapter.test.ts +58 -0
- package/src/__tests__/minijinja-counter.test.ts +61 -0
- package/src/__tests__/minijinja-query-href.test.ts +101 -0
- package/src/__tests__/minijinja-spread-attrs.test.ts +227 -0
- package/src/adapter/analysis/component-tree.ts +119 -0
- package/src/adapter/boolean-result.ts +177 -0
- package/src/adapter/emit-context.ts +119 -0
- package/src/adapter/expr/array-method.ts +346 -0
- package/src/adapter/expr/emitters.ts +608 -0
- package/src/adapter/index.ts +6 -0
- package/src/adapter/lib/constants.ts +37 -0
- package/src/adapter/lib/ir-scope.ts +95 -0
- package/src/adapter/lib/minijinja-naming.ts +85 -0
- package/src/adapter/lib/types.ts +35 -0
- package/src/adapter/memo/seed.ts +135 -0
- package/src/adapter/minijinja-adapter.ts +1796 -0
- package/src/adapter/props/prop-classes.ts +65 -0
- package/src/adapter/spread/spread-codegen.ts +168 -0
- package/src/adapter/value/parsed-literal.ts +76 -0
- package/src/build.ts +38 -0
- package/src/conformance-pins.ts +101 -0
- package/src/index.ts +12 -0
- package/src/test-render.ts +680 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prop classification for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-xslate/src/adapter/props/prop-classes.ts`.
|
|
5
|
+
* Pure functions over `ir.metadata` that derive the per-compile prop/name
|
|
6
|
+
* sets the adapter consults during lowering. No adapter instance state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ComponentIR } from '@barefootjs/jsx'
|
|
10
|
+
import { isStringTypeInfo, isBareStringLiteral } from '../value/parsed-literal.ts'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Props whose declared TS type is boolean — a bare binding of one
|
|
14
|
+
* (`data-active={props.isActive}`) must stringify as JS `String(boolean)`
|
|
15
|
+
* ("true"/"false"), not Python's `str(bool)` ("True"/"False") (#1897,
|
|
16
|
+
* pagination's data-active).
|
|
17
|
+
*/
|
|
18
|
+
export function collectBooleanTypedProps(ir: ComponentIR): Set<string> {
|
|
19
|
+
return new Set(
|
|
20
|
+
ir.metadata.propsParams
|
|
21
|
+
.filter(prop => prop.type?.primitive === 'boolean' || prop.type?.raw === 'boolean')
|
|
22
|
+
.map(prop => prop.name),
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Bare references to optional, no-default, non-primitive props (e.g.
|
|
28
|
+
* textarea's `rows`) are `None` when omitted → guarded with
|
|
29
|
+
* `is defined and is not none` in `emitExpression`. See the
|
|
30
|
+
* `nullableOptionalProps` field docstring in `minijinja-adapter.ts`.
|
|
31
|
+
*/
|
|
32
|
+
export function collectNullableOptionalProps(ir: ComponentIR): Set<string> {
|
|
33
|
+
return new Set(
|
|
34
|
+
ir.metadata.propsParams
|
|
35
|
+
.filter(
|
|
36
|
+
p =>
|
|
37
|
+
p.defaultValue === undefined &&
|
|
38
|
+
!p.isRest &&
|
|
39
|
+
p.type?.kind !== 'primitive',
|
|
40
|
+
)
|
|
41
|
+
.map(p => p.name),
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* String-typed signals and props. A signal is string-typed when its inferred
|
|
47
|
+
* type is `string` (or, defensively, when its initial value is a bare string
|
|
48
|
+
* literal); a prop when its annotated type is `string`. In the Mojo adapter
|
|
49
|
+
* this drives `eq`/`ne` selection for string equality; neither the Kolon nor
|
|
50
|
+
* the Jinja emitters consume the distinction (both languages' `==`/`!=`
|
|
51
|
+
* compare strings and numbers correctly), so this set is carried for parity
|
|
52
|
+
* with the Perl-family adapters, not because Jinja needs it today.
|
|
53
|
+
*/
|
|
54
|
+
export function collectStringValueNames(ir: ComponentIR): Set<string> {
|
|
55
|
+
const names = new Set<string>()
|
|
56
|
+
for (const s of ir.metadata.signals) {
|
|
57
|
+
if (isStringTypeInfo(s.type) || isBareStringLiteral(s.initialValue)) {
|
|
58
|
+
names.add(s.getter)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const p of ir.metadata.propsParams) {
|
|
62
|
+
if (isStringTypeInfo(p.type)) names.add(p.name)
|
|
63
|
+
}
|
|
64
|
+
return names
|
|
65
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Object-literal / conditional-spread → Jinja dict lowering for the
|
|
3
|
+
* minijinja template adapter.
|
|
4
|
+
*
|
|
5
|
+
* Near-verbatim port of
|
|
6
|
+
* `packages/adapter-jinja/src/adapter/spread/spread-codegen.ts` (itself
|
|
7
|
+
* ported from `packages/adapter-xslate/src/adapter/spread/spread-codegen.ts`).
|
|
8
|
+
* Free functions taking a `JinjaSpreadContext` (built by the adapter's
|
|
9
|
+
* `spreadCtx` getter) so the cluster depends on the narrow seam — the
|
|
10
|
+
* recursive expression entry plus per-compile bookkeeping — rather than the
|
|
11
|
+
* whole adapter class.
|
|
12
|
+
*
|
|
13
|
+
* The conditional-spread / object-literal entries read the IR-carried
|
|
14
|
+
* structured `ParsedExpr` tree (#2018) instead of re-parsing the source with
|
|
15
|
+
* `ts.createSourceFile`. The condition and scalar values are threaded
|
|
16
|
+
* straight into `ctx.convertExpressionToJinja` as its `preParsed` argument,
|
|
17
|
+
* so no stringify→re-parse round-trip occurs. The `ts.factory` rebuild in
|
|
18
|
+
* `recordIndexAccessToJinja` only reconstructs the `IDENT[KEY]` node the
|
|
19
|
+
* shared `parseRecordIndexAccess` parser accepts; no source-text re-parse.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import ts from 'typescript'
|
|
23
|
+
import { parseRecordIndexAccess, stringifyParsedExpr } from '@barefootjs/jsx'
|
|
24
|
+
import type { ParsedExpr } from '@barefootjs/jsx'
|
|
25
|
+
|
|
26
|
+
import type { JinjaSpreadContext } from '../emit-context.ts'
|
|
27
|
+
import { escapeMinijinjaSingleQuoted, minijinjaHashKey, minijinjaIdent } from '../lib/minijinja-naming.ts'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Lower a conditional inline-object spread
|
|
31
|
+
* `COND ? { 'aria-describedby': describedBy } : {}`
|
|
32
|
+
* to a Jinja inline ternary of dicts
|
|
33
|
+
* `({'aria-describedby': describedBy} if bf.truthy(describedBy) else {})`.
|
|
34
|
+
* Reads the IR-carried structured `ParsedExpr` tree; both branches must be
|
|
35
|
+
* object literals; the condition + values route through
|
|
36
|
+
* `convertExpressionToJinja`. Returns `null` for any other shape so the
|
|
37
|
+
* caller falls back to its normal lowering.
|
|
38
|
+
*/
|
|
39
|
+
export function conditionalSpreadToJinja(
|
|
40
|
+
ctx: JinjaSpreadContext,
|
|
41
|
+
expr: ParsedExpr | undefined,
|
|
42
|
+
): string | null {
|
|
43
|
+
if (!expr || expr.kind !== 'conditional') return null
|
|
44
|
+
const whenTrue = expr.consequent
|
|
45
|
+
const whenFalse = expr.alternate
|
|
46
|
+
if (whenTrue.kind !== 'object-literal' || whenFalse.kind !== 'object-literal') {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
// Thread the condition's carried `ParsedExpr` tree straight through as
|
|
50
|
+
// `preParsed` (#2018) — no stringify→re-parse round-trip.
|
|
51
|
+
const condJinja = ctx.convertConditionToJinja('', expr.test)
|
|
52
|
+
const trueJinja = objectLiteralToJinjaDict(ctx, whenTrue)
|
|
53
|
+
const falseJinja = objectLiteralToJinjaDict(ctx, whenFalse)
|
|
54
|
+
if (trueJinja === null || falseJinja === null) return null
|
|
55
|
+
return `(${trueJinja} if ${condJinja} else ${falseJinja})`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* (#1971) Lower a bare object-literal expression (`{ align: 'start' }`),
|
|
60
|
+
* carried as the IR's structured `ParsedExpr` tree, to a Jinja dict via
|
|
61
|
+
* `objectLiteralToJinjaDict`, or null when it isn't a plain object literal.
|
|
62
|
+
* Used for inline object-literal child props (carousel `opts`).
|
|
63
|
+
*/
|
|
64
|
+
export function objectLiteralExprToJinjaDict(
|
|
65
|
+
ctx: JinjaSpreadContext,
|
|
66
|
+
expr: ParsedExpr | undefined,
|
|
67
|
+
): string | null {
|
|
68
|
+
if (!expr || expr.kind !== 'object-literal') return null
|
|
69
|
+
return objectLiteralToJinjaDict(ctx, expr)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Convert a static object literal into a Jinja dict string for a
|
|
74
|
+
* conditional spread. Only static string/identifier keys are allowed; values
|
|
75
|
+
* resolve via `convertExpressionToJinja` (or the `Record[propKey]` index
|
|
76
|
+
* lowering). Returns `null` for any computed/spread/dynamic key. Empty
|
|
77
|
+
* object → `{}`.
|
|
78
|
+
*/
|
|
79
|
+
export function objectLiteralToJinjaDict(
|
|
80
|
+
ctx: JinjaSpreadContext,
|
|
81
|
+
obj: Extract<ParsedExpr, { kind: 'object-literal' }>,
|
|
82
|
+
): string | null {
|
|
83
|
+
const entries: string[] = []
|
|
84
|
+
for (const prop of obj.properties) {
|
|
85
|
+
// Shorthand `{ a }` was a `ShorthandPropertyAssignment` (not a
|
|
86
|
+
// `PropertyAssignment`), so the former parser rejected it — keep refusing.
|
|
87
|
+
if (prop.shorthand) return null
|
|
88
|
+
// A numeric key (`{ 1: x }`) was rejected by the former parser (only
|
|
89
|
+
// identifier / string-literal names were accepted); `keyKind`
|
|
90
|
+
// distinguishes it from a same-text string `'1'` key.
|
|
91
|
+
if (prop.keyKind === 'numeric') return null
|
|
92
|
+
const key = prop.key
|
|
93
|
+
const val = prop.value
|
|
94
|
+
const indexed = recordIndexAccessToJinja(ctx, val)
|
|
95
|
+
if (
|
|
96
|
+
indexed === null &&
|
|
97
|
+
val.kind === 'index-access' &&
|
|
98
|
+
!isLiteralIndex(val.index)
|
|
99
|
+
) {
|
|
100
|
+
// Variable-index record access (`sizeMap[size]`) the static-inline
|
|
101
|
+
// path couldn't resolve (non-scalar value / non-const receiver).
|
|
102
|
+
// Record BF101 and bail so the spread surfaces the out-of-shape
|
|
103
|
+
// diagnostic, matching the Kolon port.
|
|
104
|
+
ctx.errors.push({
|
|
105
|
+
code: 'BF101',
|
|
106
|
+
severity: 'error',
|
|
107
|
+
message: `Spread object value '${stringifyParsedExpr(val)}' indexes a record map whose values aren't scalar literals — it can't lower to an inline Jinja dict.`,
|
|
108
|
+
loc: { file: ctx.componentName + '.tsx', start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
|
|
109
|
+
suggestion: {
|
|
110
|
+
message: 'Index a record whose values are number/string literals, or move the spread into a \'use client\' component so hydration computes it.',
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
const valJinja =
|
|
116
|
+
indexed !== null
|
|
117
|
+
? indexed
|
|
118
|
+
// Thread the carried `val` tree straight through as `preParsed`
|
|
119
|
+
// (#2018) — no stringify→re-parse round-trip.
|
|
120
|
+
: ctx.convertExpressionToJinja('', val)
|
|
121
|
+
entries.push(`${minijinjaHashKey(key)}: ${valJinja}`)
|
|
122
|
+
}
|
|
123
|
+
return entries.length === 0 ? '{}' : `{${entries.join(', ')}}`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** True when a parsed index is a numeric or string literal (`arr[0]`, `m['k']`). */
|
|
127
|
+
function isLiteralIndex(index: ParsedExpr): boolean {
|
|
128
|
+
return (
|
|
129
|
+
index.kind === 'literal' &&
|
|
130
|
+
(index.literalType === 'number' || index.literalType === 'string')
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Lower a spread-object VALUE of the form `IDENT[KEY]` (CheckIcon's
|
|
136
|
+
* `sizeMap[size]`) to an inline indexed Jinja dict
|
|
137
|
+
* `{'sm': 16, 'md': 20, ...}[size]`.
|
|
138
|
+
* Reuses the shared structural parse (`parseRecordIndexAccess`) — rebuilding
|
|
139
|
+
* the `IDENT[KEY]` node from the carried tree via `ts.factory` rather than
|
|
140
|
+
* re-parsing source text; this wrapper only does the single-quote escaping +
|
|
141
|
+
* bracket-index emit. Jinja indexes a dict literal with the SAME bracket
|
|
142
|
+
* syntax `{…}[key]` a JS object index would use — no Kolon-style divergence
|
|
143
|
+
* to steer around here.
|
|
144
|
+
*/
|
|
145
|
+
export function recordIndexAccessToJinja(ctx: JinjaSpreadContext, val: ParsedExpr): string | null {
|
|
146
|
+
// The only shape `parseRecordIndexAccess` accepts is `IDENT[KEY]` with
|
|
147
|
+
// identifier object and index, so rebuild exactly that node from the
|
|
148
|
+
// carried tree via `ts.factory` — no source-text re-parse.
|
|
149
|
+
if (
|
|
150
|
+
val.kind !== 'index-access' ||
|
|
151
|
+
val.object.kind !== 'identifier' ||
|
|
152
|
+
val.index.kind !== 'identifier'
|
|
153
|
+
) {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
const tsVal = ts.factory.createElementAccessExpression(
|
|
157
|
+
ts.factory.createIdentifier(val.object.name),
|
|
158
|
+
ts.factory.createIdentifier(val.index.name),
|
|
159
|
+
)
|
|
160
|
+
const parsed = parseRecordIndexAccess(tsVal, ctx.localConstants ?? [], ctx.propsParams)
|
|
161
|
+
if (!parsed) return null
|
|
162
|
+
const entries = parsed.entries.map(e => {
|
|
163
|
+
const mapVal =
|
|
164
|
+
e.value.kind === 'number' ? e.value.text : `'${escapeMinijinjaSingleQuoted(e.value.text)}'`
|
|
165
|
+
return `${minijinjaHashKey(e.key)}: ${mapVal}`
|
|
166
|
+
})
|
|
167
|
+
return `{${entries.join(', ')}}[${minijinjaIdent(parsed.indexPropName)}]`
|
|
168
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String-literal value lowering for the minijinja template adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ported from `packages/adapter-xslate/src/adapter/value/parsed-literal.ts`.
|
|
5
|
+
* Pure functions over const-initializer source text and analyzer type info —
|
|
6
|
+
* no adapter instance state. Byte-identical logic to the Xslate copy (the
|
|
7
|
+
* source-text parsing here is language-agnostic; only the caller's escaping
|
|
8
|
+
* of the resolved value into a template literal differs, and that happens at
|
|
9
|
+
* the call site in `minijinja-adapter.ts`, not here).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { evalStringArrayJoin, type TypeInfo } from '@barefootjs/jsx'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse a const initializer's source text. Returns the unescaped string value
|
|
16
|
+
* when the whole initializer is a single pure string literal — single/double
|
|
17
|
+
* quoted, or a no-substitution backtick template (no `${}`) — else `null`.
|
|
18
|
+
* Only such a value can be inlined byte-for-byte; template literals with
|
|
19
|
+
* interpolation, numbers, objects, and `Record<T,string>` maps are excluded.
|
|
20
|
+
*/
|
|
21
|
+
export function parsePureStringLiteral(source: string): string | null {
|
|
22
|
+
let s = source.trim()
|
|
23
|
+
// Peel a single layer of wrapping parens.
|
|
24
|
+
while (s.startsWith('(') && s.endsWith(')')) s = s.slice(1, -1).trim()
|
|
25
|
+
const quote = s[0]
|
|
26
|
+
if ((quote === "'" || quote === '"') && s[s.length - 1] === quote) {
|
|
27
|
+
const body = s.slice(1, -1)
|
|
28
|
+
// Reject if an unescaped matching quote appears inside (not a single
|
|
29
|
+
// literal then).
|
|
30
|
+
if (containsUnescaped(body, quote)) return null
|
|
31
|
+
return unescapeStringLiteralBody(body)
|
|
32
|
+
}
|
|
33
|
+
if (quote === '`' && s[s.length - 1] === '`') {
|
|
34
|
+
const body = s.slice(1, -1)
|
|
35
|
+
if (body.includes('${')) return null
|
|
36
|
+
if (containsUnescaped(body, '`')) return null
|
|
37
|
+
return unescapeStringLiteralBody(body)
|
|
38
|
+
}
|
|
39
|
+
// `[<literals>].join(' ')` module consts (e.g. Switch's `trackStateClasses`)
|
|
40
|
+
// → inline the flattened string byte-for-byte. See `evalStringArrayJoin`.
|
|
41
|
+
return evalStringArrayJoin(source)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Whether `s` contains an unescaped occurrence of `ch`. */
|
|
45
|
+
export function containsUnescaped(s: string, ch: string): boolean {
|
|
46
|
+
for (let i = 0; i < s.length; i++) {
|
|
47
|
+
if (s[i] === '\\') { i++; continue }
|
|
48
|
+
if (s[i] === ch) return true
|
|
49
|
+
}
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Unescape a JS string-literal body's common escape sequences. */
|
|
54
|
+
export function unescapeStringLiteralBody(s: string): string {
|
|
55
|
+
return s.replace(/\\(.)/g, (_, c) => {
|
|
56
|
+
switch (c) {
|
|
57
|
+
case 'n': return '\n'
|
|
58
|
+
case 'r': return '\r'
|
|
59
|
+
case 't': return '\t'
|
|
60
|
+
case '0': return '\0'
|
|
61
|
+
default: return c
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** True when `type` is the `string` primitive. */
|
|
67
|
+
export function isStringTypeInfo(type: TypeInfo | undefined): boolean {
|
|
68
|
+
return type?.kind === 'primitive' && type.primitive === 'string'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** True when `initialValue` is a bare string-literal expression. */
|
|
72
|
+
export function isBareStringLiteral(initialValue: string | undefined): boolean {
|
|
73
|
+
if (!initialValue) return false
|
|
74
|
+
const v = initialValue.trim()
|
|
75
|
+
return (v.startsWith("'") && v.endsWith("'")) || (v.startsWith('"') && v.endsWith('"'))
|
|
76
|
+
}
|
package/src/build.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// minijinja (Rust) build config factory for barefoot.config.ts
|
|
2
|
+
|
|
3
|
+
import type { BuildOptions } from '@barefootjs/jsx'
|
|
4
|
+
import { MinijinjaAdapter } from './adapter/index.ts'
|
|
5
|
+
import type { MinijinjaAdapterOptions } from './adapter/index.ts'
|
|
6
|
+
|
|
7
|
+
export interface MinijinjaBuildOptions extends BuildOptions {
|
|
8
|
+
/** Adapter-specific options passed to MinijinjaAdapter */
|
|
9
|
+
adapterOptions?: MinijinjaAdapterOptions
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a BarefootBuildConfig for minijinja (Jinja2-compatible) template
|
|
14
|
+
* projects.
|
|
15
|
+
*
|
|
16
|
+
* Uses structural typing — does not import BarefootBuildConfig to avoid a
|
|
17
|
+
* circular dependency between @barefootjs/rust and @barefootjs/cli.
|
|
18
|
+
*/
|
|
19
|
+
export function createConfig(options: MinijinjaBuildOptions = {}) {
|
|
20
|
+
return {
|
|
21
|
+
adapter: new MinijinjaAdapter(options.adapterOptions),
|
|
22
|
+
paths: options.paths,
|
|
23
|
+
components: options.components,
|
|
24
|
+
outDir: options.outDir,
|
|
25
|
+
minify: options.minify,
|
|
26
|
+
contentHash: options.contentHash,
|
|
27
|
+
externals: options.externals,
|
|
28
|
+
externalsBasePath: options.externalsBasePath,
|
|
29
|
+
bundleEntries: options.bundleEntries,
|
|
30
|
+
localImportPrefixes: options.localImportPrefixes,
|
|
31
|
+
outputLayout: options.outputLayout ?? {
|
|
32
|
+
templates: 'templates',
|
|
33
|
+
clientJs: 'client',
|
|
34
|
+
runtime: 'client',
|
|
35
|
+
},
|
|
36
|
+
postBuild: options.postBuild,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-fixture build-time contracts for shapes the adapter intentionally
|
|
3
|
+
* refuses to lower. Mirrors adapter-jinja's set (itself mirroring
|
|
4
|
+
* xslate's) — the lowering gates are shared code paths in the ported
|
|
5
|
+
* adapter (BF103/BF104 are structural: cross-template child registration
|
|
6
|
+
* / destructure-loop-param limits that apply identically regardless of
|
|
7
|
+
* target template language or render engine). Consumed by this package's
|
|
8
|
+
* own conformance test (as `expectedDiagnostics`) and by `bf compat`
|
|
9
|
+
* (issue-URL attribution).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ConformancePins } from '@barefootjs/jsx'
|
|
13
|
+
|
|
14
|
+
export const conformancePins: ConformancePins = {
|
|
15
|
+
// Sibling-imported child component in a loop body: emits a
|
|
16
|
+
// cross-template call needing separate registration. BF103 makes
|
|
17
|
+
// the requirement loud (same as xslate).
|
|
18
|
+
'static-array-children': [{ code: 'BF103', severity: 'error' }],
|
|
19
|
+
// TodoApp / TodoAppSSR import `TodoItem` from a sibling file and
|
|
20
|
+
// call it inside a keyed `.map`. Same BF103 (imported child in
|
|
21
|
+
// `.map`) as xslate.
|
|
22
|
+
'todo-app': [{ code: 'BF103', severity: 'error' }],
|
|
23
|
+
'todo-app-ssr': [{ code: 'BF103', severity: 'error' }],
|
|
24
|
+
// The `([emoji, users]) => ...` / `([id, t]) => ...` params in these two
|
|
25
|
+
// fixtures no longer trip BF104 — the destructure itself now lowers
|
|
26
|
+
// cleanly to a native `{% set %}` accessor (#2087 Phase B). But both
|
|
27
|
+
// fixtures' loop array is `entries`, a bare identifier bound to a
|
|
28
|
+
// function-scope local const with a NON-inlineable initializer
|
|
29
|
+
// (`Object.entries(props.x ?? {}).filter(...)`) — a pre-existing,
|
|
30
|
+
// orthogonal limitation this adapter has always had (it only ever
|
|
31
|
+
// INLINES a local const's value at its use site — numeric/string
|
|
32
|
+
// literal, or a static-record-literal lookup — never binds one as a
|
|
33
|
+
// `{% set %}` template local), just never reachable before because the
|
|
34
|
+
// narrower pre-#2087 gate refused the destructure param first. Left
|
|
35
|
+
// unhandled it would silently render an EMPTY list (minijinja's
|
|
36
|
+
// `UndefinedBehavior::Chainable` tolerates iterating an unbound name as
|
|
37
|
+
// zero iterations) instead of failing loudly, so `renderLoop` in
|
|
38
|
+
// `minijinja-adapter.ts` now detects this shape and raises BF101
|
|
39
|
+
// instead — see the "Loop array `<name>` is a local computed value"
|
|
40
|
+
// diagnostic (mirrors adapter-jinja's identical check). Fixing the
|
|
41
|
+
// underlying gap (computed-array-from-props as a loop source) is out of
|
|
42
|
+
// scope for #2087; tracked as a follow-up at
|
|
43
|
+
// https://github.com/piconic-ai/barefootjs/issues/2087.
|
|
44
|
+
'static-array-from-props': [
|
|
45
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2087' },
|
|
46
|
+
],
|
|
47
|
+
// Both BF103 (imported child) and BF101 (unresolvable computed loop
|
|
48
|
+
// array, see above) fire.
|
|
49
|
+
'static-array-from-props-with-component': [
|
|
50
|
+
{ code: 'BF103', severity: 'error' },
|
|
51
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2087' },
|
|
52
|
+
],
|
|
53
|
+
// Rest-destructure `.map()` callbacks (#2087 Phase B): every shape now
|
|
54
|
+
// lowers natively — fixed bindings at any depth/shape via a chained
|
|
55
|
+
// Jinja accessor built off `LoopParamBinding.segments`
|
|
56
|
+
// (`minijinjaAccessorFromSegments`), array-rest via the runtime's
|
|
57
|
+
// `bf.slice`, and object-rest (read via member access OR spread onto an
|
|
58
|
+
// intrinsic element) via a TRUE residual dict from the new `bf.omit`
|
|
59
|
+
// runtime helper. No `expectedDiagnostics` pins remain for any of the
|
|
60
|
+
// `rest-destructure-*-in-map` / `destructure-*-in-map` fixtures — see
|
|
61
|
+
// `rest-destructure-object-spread-in-map` for the residual-spread case
|
|
62
|
+
// and `destructure-array-index-in-map` / `destructure-nested-object-in-map`
|
|
63
|
+
// for the no-rest fixed-binding shapes.
|
|
64
|
+
// (button/kbd graduated: the site/ui Button/Kbd `<Slot>` `{...props}` /
|
|
65
|
+
// `{...children.props}` component-spread now lowers via nested
|
|
66
|
+
// `dict(base, **top)` calls — see `minijinja-adapter.ts`'s
|
|
67
|
+
// `renderComponent` — instead of refusing with BF101, so these two no
|
|
68
|
+
// longer need a pin here.)
|
|
69
|
+
// (`tagged-template-classname` graduated by #2092 — the tag resolves
|
|
70
|
+
// through the interleave-tag catalogue and desugars to an untagged
|
|
71
|
+
// template literal, so it lowers like any other className template.)
|
|
72
|
+
// #2038: a filter predicate whose body contains a NESTED callback call
|
|
73
|
+
// (`t => !picked().some(p => …)` / `t => picked().find(p => …)`). Jinja
|
|
74
|
+
// has no inline comprehension-with-nested-callback form usable from the
|
|
75
|
+
// evaluator-JSON `*_eval` payload mechanism (this adapter's ONLY
|
|
76
|
+
// higher-order-callback lowering path — see `minijinja-adapter.ts`'s file
|
|
77
|
+
// header, divergence 3), so the compiler is loud (BF101) instead of
|
|
78
|
+
// lossy, same as xslate. The `/* @client */` twin
|
|
79
|
+
// (`filter-nested-callback-predicate-client`) has no pin here: it must
|
|
80
|
+
// render clean on every adapter, which asserts the suppression contract.
|
|
81
|
+
// https://github.com/piconic-ai/barefootjs/issues/2038
|
|
82
|
+
'filter-nested-callback-predicate': [
|
|
83
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
|
|
84
|
+
],
|
|
85
|
+
'filter-nested-find-predicate': [
|
|
86
|
+
{ code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
|
|
87
|
+
],
|
|
88
|
+
// NB: TOP-LEVEL `.find` / `.findIndex` / `.findLast` / `.findLastIndex`
|
|
89
|
+
// (text position) are NOT pinned here — like xslate (unlike mojo, which
|
|
90
|
+
// refuses them), Jinja lowers them to `bf.find_eval` / `find_index_eval`
|
|
91
|
+
// / etc. via the same evaluator-JSON mechanism as `.filter` / `.every` /
|
|
92
|
+
// `.some`, so they render. Only the NESTED-in-a-predicate form above is
|
|
93
|
+
// refused (#2038).
|
|
94
|
+
// #2073 follow-up (same as xslate): a function-reference `.map(format)`
|
|
95
|
+
// callback has no arrow body to serialize — not a CALLBACK_METHODS shape
|
|
96
|
+
// (`asCallbackMethodCall` requires an arrow argument) — so the shared
|
|
97
|
+
// `isSupported`'s `UNSUPPORTED_METHODS` gate refuses it with the generic
|
|
98
|
+
// "Expression not supported" BF101 rather than emitting a broken
|
|
99
|
+
// template.
|
|
100
|
+
'array-map-function-reference': [{ code: 'BF101', severity: 'error' }],
|
|
101
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BarefootJS minijinja (Rust) Template Adapter
|
|
3
|
+
*
|
|
4
|
+
* Generates Jinja2-compatible template files (.j2) from BarefootJS IR,
|
|
5
|
+
* rendered by the `minijinja` Rust crate. Near-verbatim port of
|
|
6
|
+
* `@barefootjs/jinja` — see `adapter/minijinja-adapter.ts`'s file header for
|
|
7
|
+
* the full design record.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { MinijinjaAdapter, minijinjaAdapter } from './adapter/index.ts'
|
|
11
|
+
export type { MinijinjaAdapterOptions } from './adapter/index.ts'
|
|
12
|
+
export { conformancePins } from './conformance-pins.ts'
|