@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.
Files changed (87) hide show
  1. package/README.md +194 -0
  2. package/dist/adapter/analysis/component-tree.d.ts +26 -0
  3. package/dist/adapter/analysis/component-tree.d.ts.map +1 -0
  4. package/dist/adapter/boolean-result.d.ts +85 -0
  5. package/dist/adapter/boolean-result.d.ts.map +1 -0
  6. package/dist/adapter/emit-context.d.ts +107 -0
  7. package/dist/adapter/emit-context.d.ts.map +1 -0
  8. package/dist/adapter/expr/array-method.d.ts +75 -0
  9. package/dist/adapter/expr/array-method.d.ts.map +1 -0
  10. package/dist/adapter/expr/emitters.d.ts +143 -0
  11. package/dist/adapter/expr/emitters.d.ts.map +1 -0
  12. package/dist/adapter/index.d.ts +6 -0
  13. package/dist/adapter/index.d.ts.map +1 -0
  14. package/dist/adapter/index.js +189091 -0
  15. package/dist/adapter/lib/constants.d.ts +25 -0
  16. package/dist/adapter/lib/constants.d.ts.map +1 -0
  17. package/dist/adapter/lib/ir-scope.d.ts +50 -0
  18. package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
  19. package/dist/adapter/lib/minijinja-naming.d.ts +64 -0
  20. package/dist/adapter/lib/minijinja-naming.d.ts.map +1 -0
  21. package/dist/adapter/lib/types.d.ts +32 -0
  22. package/dist/adapter/lib/types.d.ts.map +1 -0
  23. package/dist/adapter/memo/seed.d.ts +84 -0
  24. package/dist/adapter/memo/seed.d.ts.map +1 -0
  25. package/dist/adapter/minijinja-adapter.d.ts +421 -0
  26. package/dist/adapter/minijinja-adapter.d.ts.map +1 -0
  27. package/dist/adapter/props/prop-classes.d.ts +33 -0
  28. package/dist/adapter/props/prop-classes.d.ts.map +1 -0
  29. package/dist/adapter/spread/spread-codegen.d.ts +63 -0
  30. package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
  31. package/dist/adapter/value/parsed-literal.d.ts +28 -0
  32. package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
  33. package/dist/build.d.ts +29 -0
  34. package/dist/build.d.ts.map +1 -0
  35. package/dist/build.js +189111 -0
  36. package/dist/conformance-pins.d.ts +13 -0
  37. package/dist/conformance-pins.d.ts.map +1 -0
  38. package/dist/index.d.ts +12 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +189112 -0
  41. package/package.json +67 -0
  42. package/runtime/Cargo.lock +124 -0
  43. package/runtime/Cargo.toml +21 -0
  44. package/runtime/src/backend_minijinja.rs +176 -0
  45. package/runtime/src/bin/bf-render.rs +147 -0
  46. package/runtime/src/evaluator.rs +770 -0
  47. package/runtime/src/lib.rs +19 -0
  48. package/runtime/src/manifest.rs +258 -0
  49. package/runtime/src/num.rs +558 -0
  50. package/runtime/src/runtime.rs +1548 -0
  51. package/runtime/src/search_params.rs +173 -0
  52. package/runtime/tests/eval_vectors.rs +94 -0
  53. package/runtime/tests/evaluator.rs +407 -0
  54. package/runtime/tests/helper_vectors.rs +348 -0
  55. package/runtime/tests/manifest.rs +169 -0
  56. package/runtime/tests/omit.rs +79 -0
  57. package/runtime/tests/props_attr.rs +75 -0
  58. package/runtime/tests/query.rs +50 -0
  59. package/runtime/tests/render_child.rs +210 -0
  60. package/runtime/tests/search_params.rs +68 -0
  61. package/runtime/tests/spread_attrs.rs +94 -0
  62. package/runtime/tests/template_primitives.rs +376 -0
  63. package/runtime/tests/vector-divergences.json +33 -0
  64. package/src/__tests__/minijinja-adapter-unit.test.ts +392 -0
  65. package/src/__tests__/minijinja-adapter.test.ts +58 -0
  66. package/src/__tests__/minijinja-counter.test.ts +61 -0
  67. package/src/__tests__/minijinja-query-href.test.ts +101 -0
  68. package/src/__tests__/minijinja-spread-attrs.test.ts +227 -0
  69. package/src/adapter/analysis/component-tree.ts +119 -0
  70. package/src/adapter/boolean-result.ts +177 -0
  71. package/src/adapter/emit-context.ts +119 -0
  72. package/src/adapter/expr/array-method.ts +346 -0
  73. package/src/adapter/expr/emitters.ts +608 -0
  74. package/src/adapter/index.ts +6 -0
  75. package/src/adapter/lib/constants.ts +37 -0
  76. package/src/adapter/lib/ir-scope.ts +95 -0
  77. package/src/adapter/lib/minijinja-naming.ts +85 -0
  78. package/src/adapter/lib/types.ts +35 -0
  79. package/src/adapter/memo/seed.ts +135 -0
  80. package/src/adapter/minijinja-adapter.ts +1796 -0
  81. package/src/adapter/props/prop-classes.ts +65 -0
  82. package/src/adapter/spread/spread-codegen.ts +168 -0
  83. package/src/adapter/value/parsed-literal.ts +76 -0
  84. package/src/build.ts +38 -0
  85. package/src/conformance-pins.ts +101 -0
  86. package/src/index.ts +12 -0
  87. 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'