@barefootjs/blade 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 (72) hide show
  1. package/README.md +73 -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/blade-adapter.d.ts +537 -0
  5. package/dist/adapter/blade-adapter.d.ts.map +1 -0
  6. package/dist/adapter/boolean-result.d.ts +76 -0
  7. package/dist/adapter/boolean-result.d.ts.map +1 -0
  8. package/dist/adapter/emit-context.d.ts +105 -0
  9. package/dist/adapter/emit-context.d.ts.map +1 -0
  10. package/dist/adapter/expr/array-method.d.ts +74 -0
  11. package/dist/adapter/expr/array-method.d.ts.map +1 -0
  12. package/dist/adapter/expr/emitters.d.ts +176 -0
  13. package/dist/adapter/expr/emitters.d.ts.map +1 -0
  14. package/dist/adapter/expr/operand.d.ts +25 -0
  15. package/dist/adapter/expr/operand.d.ts.map +1 -0
  16. package/dist/adapter/index.d.ts +6 -0
  17. package/dist/adapter/index.d.ts.map +1 -0
  18. package/dist/adapter/index.js +189060 -0
  19. package/dist/adapter/lib/blade-naming.d.ts +128 -0
  20. package/dist/adapter/lib/blade-naming.d.ts.map +1 -0
  21. package/dist/adapter/lib/constants.d.ts +21 -0
  22. package/dist/adapter/lib/constants.d.ts.map +1 -0
  23. package/dist/adapter/lib/ir-scope.d.ts +48 -0
  24. package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
  25. package/dist/adapter/lib/types.d.ts +27 -0
  26. package/dist/adapter/lib/types.d.ts.map +1 -0
  27. package/dist/adapter/memo/seed.d.ts +84 -0
  28. package/dist/adapter/memo/seed.d.ts.map +1 -0
  29. package/dist/adapter/props/prop-classes.d.ts +34 -0
  30. package/dist/adapter/props/prop-classes.d.ts.map +1 -0
  31. package/dist/adapter/spread/spread-codegen.d.ts +72 -0
  32. package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
  33. package/dist/adapter/value/parsed-literal.d.ts +27 -0
  34. package/dist/adapter/value/parsed-literal.d.ts.map +1 -0
  35. package/dist/build.d.ts +28 -0
  36. package/dist/build.d.ts.map +1 -0
  37. package/dist/build.js +189080 -0
  38. package/dist/conformance-pins.d.ts +12 -0
  39. package/dist/conformance-pins.d.ts.map +1 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +189079 -0
  43. package/package.json +67 -0
  44. package/php/composer.json +32 -0
  45. package/php/src/BladeBackend.php +197 -0
  46. package/php/src/naming.php +84 -0
  47. package/php/tests/test_render.php +159 -0
  48. package/src/__tests__/blade-adapter-unit.test.ts +392 -0
  49. package/src/__tests__/blade-adapter.test.ts +52 -0
  50. package/src/__tests__/blade-counter.test.ts +63 -0
  51. package/src/__tests__/blade-query-href.test.ts +113 -0
  52. package/src/__tests__/blade-spread-attrs.test.ts +235 -0
  53. package/src/adapter/analysis/component-tree.ts +119 -0
  54. package/src/adapter/blade-adapter.ts +1912 -0
  55. package/src/adapter/boolean-result.ts +168 -0
  56. package/src/adapter/emit-context.ts +117 -0
  57. package/src/adapter/expr/array-method.ts +345 -0
  58. package/src/adapter/expr/emitters.ts +636 -0
  59. package/src/adapter/expr/operand.ts +35 -0
  60. package/src/adapter/index.ts +6 -0
  61. package/src/adapter/lib/blade-naming.ts +180 -0
  62. package/src/adapter/lib/constants.ts +33 -0
  63. package/src/adapter/lib/ir-scope.ts +83 -0
  64. package/src/adapter/lib/types.ts +30 -0
  65. package/src/adapter/memo/seed.ts +135 -0
  66. package/src/adapter/props/prop-classes.ts +66 -0
  67. package/src/adapter/spread/spread-codegen.ts +179 -0
  68. package/src/adapter/value/parsed-literal.ts +75 -0
  69. package/src/build.ts +37 -0
  70. package/src/conformance-pins.ts +86 -0
  71. package/src/index.ts +9 -0
  72. package/src/test-render.ts +782 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Object-literal / conditional-spread → Blade hash lowering for the Blade
3
+ * template adapter.
4
+ *
5
+ * Ported from `packages/adapter-jinja/src/adapter/spread/spread-codegen.ts`.
6
+ * Free functions taking a `BladeSpreadContext` (built by the adapter's
7
+ * `spreadCtx` getter) so the cluster depends on the narrow seam — the
8
+ * recursive expression entry plus per-compile bookkeeping — rather than the
9
+ * whole adapter class.
10
+ *
11
+ * The conditional-spread / object-literal entries read the IR-carried
12
+ * structured `ParsedExpr` tree (#2018) instead of re-parsing the source with
13
+ * `ts.createSourceFile`. The condition and scalar values are threaded
14
+ * straight into `ctx.convertExpressionToBlade` as its `preParsed` argument,
15
+ * so no stringify→re-parse round-trip occurs. The `ts.factory` rebuild in
16
+ * `recordIndexAccessToBlade` only reconstructs the `IDENT[KEY]` node the
17
+ * shared `parseRecordIndexAccess` parser accepts; no source-text re-parse.
18
+ *
19
+ * Twig→Blade divergence: a Twig hash literal `{'k': v}` becomes a PHP array
20
+ * literal `['k' => v]` (mapping table) — `'key' => value` entries joined by
21
+ * `, `, wrapped in `[...]`, never `{...}`. Empty hash is `[]`, not `{}`.
22
+ */
23
+
24
+ import ts from 'typescript'
25
+ import { parseRecordIndexAccess, stringifyParsedExpr } from '@barefootjs/jsx'
26
+ import type { ParsedExpr } from '@barefootjs/jsx'
27
+
28
+ import type { BladeSpreadContext } from '../emit-context.ts'
29
+ import { escapeBladeSingleQuoted, bladeHashKey, bladeVar } from '../lib/blade-naming.ts'
30
+
31
+ /**
32
+ * Lower a conditional inline-object spread
33
+ * `COND ? { 'aria-describedby': describedBy } : {}`
34
+ * to a Blade inline ternary of PHP arrays
35
+ * `($bf->truthy($describedBy) ? ['aria-describedby' => $describedBy] : [])`.
36
+ * Reads the IR-carried structured `ParsedExpr` tree; both branches must be
37
+ * object literals; the condition + values route through
38
+ * `convertExpressionToBlade`. Returns `null` for any other shape so the
39
+ * caller falls back to its normal lowering.
40
+ */
41
+ export function conditionalSpreadToBlade(
42
+ ctx: BladeSpreadContext,
43
+ expr: ParsedExpr | undefined,
44
+ ): string | null {
45
+ if (!expr || expr.kind !== 'conditional') return null
46
+ const whenTrue = expr.consequent
47
+ const whenFalse = expr.alternate
48
+ if (whenTrue.kind !== 'object-literal' || whenFalse.kind !== 'object-literal') {
49
+ return null
50
+ }
51
+ // Thread the condition's carried `ParsedExpr` tree straight through as
52
+ // `preParsed` (#2018) — no stringify→re-parse round-trip.
53
+ const condBlade = ctx.convertConditionToBlade('', expr.test)
54
+ const trueBlade = objectLiteralToBladeDict(ctx, whenTrue)
55
+ const falseBlade = objectLiteralToBladeDict(ctx, whenFalse)
56
+ if (trueBlade === null || falseBlade === null) return null
57
+ // Blade's symbolic ternary (see `expr/emitters.ts`'s file header,
58
+ // divergence 1) — `(test ? a : b)`, not Jinja's `(a if test else b)`.
59
+ return `(${condBlade} ? ${trueBlade} : ${falseBlade})`
60
+ }
61
+
62
+ /**
63
+ * (#1971) Lower a bare object-literal expression (`{ align: 'start' }`),
64
+ * carried as the IR's structured `ParsedExpr` tree, to a PHP array via
65
+ * `objectLiteralToBladeDict`, or null when it isn't a plain object literal.
66
+ * Used for inline object-literal child props (carousel `opts`).
67
+ */
68
+ export function objectLiteralExprToBladeDict(
69
+ ctx: BladeSpreadContext,
70
+ expr: ParsedExpr | undefined,
71
+ ): string | null {
72
+ if (!expr || expr.kind !== 'object-literal') return null
73
+ return objectLiteralToBladeDict(ctx, expr)
74
+ }
75
+
76
+ /**
77
+ * Convert a static object literal into a PHP array-literal string for a
78
+ * conditional spread. Only static string/identifier keys are allowed; values
79
+ * resolve via `convertExpressionToBlade` (or the `Record[propKey]` index
80
+ * lowering). Returns `null` for any computed/spread/dynamic key. Empty
81
+ * object → `[]`.
82
+ */
83
+ export function objectLiteralToBladeDict(
84
+ ctx: BladeSpreadContext,
85
+ obj: Extract<ParsedExpr, { kind: 'object-literal' }>,
86
+ ): string | null {
87
+ const entries: string[] = []
88
+ for (const prop of obj.properties) {
89
+ // Shorthand `{ a }` was a `ShorthandPropertyAssignment` (not a
90
+ // `PropertyAssignment`), so the former parser rejected it — keep refusing.
91
+ if (prop.shorthand) return null
92
+ // A numeric key (`{ 1: x }`) was rejected by the former parser (only
93
+ // identifier / string-literal names were accepted); `keyKind`
94
+ // distinguishes it from a same-text string `'1'` key.
95
+ if (prop.keyKind === 'numeric') return null
96
+ const key = prop.key
97
+ const val = prop.value
98
+ const indexed = recordIndexAccessToBlade(ctx, val)
99
+ if (
100
+ indexed === null &&
101
+ val.kind === 'index-access' &&
102
+ !isLiteralIndex(val.index)
103
+ ) {
104
+ // Variable-index record access (`sizeMap[size]`) the static-inline
105
+ // path couldn't resolve (non-scalar value / non-const receiver).
106
+ // Record BF101 and bail so the spread surfaces the out-of-shape
107
+ // diagnostic, matching the Jinja port.
108
+ ctx.errors.push({
109
+ code: 'BF101',
110
+ severity: 'error',
111
+ message: `Spread object value '${stringifyParsedExpr(val)}' indexes a record map whose values aren't scalar literals — it can't lower to an inline Blade hash.`,
112
+ loc: { file: ctx.componentName + '.tsx', start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
113
+ suggestion: {
114
+ message: 'Index a record whose values are number/string literals, or move the spread into a \'use client\' component so hydration computes it.',
115
+ },
116
+ })
117
+ return null
118
+ }
119
+ const valBlade =
120
+ indexed !== null
121
+ ? indexed
122
+ // Thread the carried `val` tree straight through as `preParsed`
123
+ // (#2018) — no stringify→re-parse round-trip.
124
+ : ctx.convertExpressionToBlade('', val)
125
+ entries.push(`${bladeHashKey(key)} => ${valBlade}`)
126
+ }
127
+ return entries.length === 0 ? '[]' : `[${entries.join(', ')}]`
128
+ }
129
+
130
+ /** True when a parsed index is a numeric or string literal (`arr[0]`, `m['k']`). */
131
+ function isLiteralIndex(index: ParsedExpr): boolean {
132
+ return (
133
+ index.kind === 'literal' &&
134
+ (index.literalType === 'number' || index.literalType === 'string')
135
+ )
136
+ }
137
+
138
+ /**
139
+ * Lower a spread-object VALUE of the form `IDENT[KEY]` (CheckIcon's
140
+ * `sizeMap[size]`) to an inline indexed PHP array
141
+ * `['sm' => 16, 'md' => 20, ...][$size]`.
142
+ * Reuses the shared structural parse (`parseRecordIndexAccess`) — rebuilding
143
+ * the `IDENT[KEY]` node from the carried tree via `ts.factory` rather than
144
+ * re-parsing source text; this wrapper only does the single-quote escaping +
145
+ * bracket-index emit. PHP indexes an array literal with the SAME bracket
146
+ * syntax `[...][key]` a JS object index would use (verified empirically —
147
+ * `php -r 'var_dump(["a"=>1]["a"]);'` → `1`) — no Kolon-style divergence to
148
+ * steer around here. Unlike Twig's own hash `{…}[key]`, a MISSING key here
149
+ * would normally raise a PHP warning rather than resolve to `null` — this
150
+ * call site's caller (`objectLiteralToBladeDict`'s data-table lookup path,
151
+ * and `blade-adapter.ts`'s `${MAP}[key]` template-literal lowering) always
152
+ * wraps the result in `?? ''`/`?? null`, which silences an undefined-index
153
+ * warning exactly like it silences an undefined-variable one (see
154
+ * `blade-adapter.ts`'s file header, the `??` divergence).
155
+ */
156
+ export function recordIndexAccessToBlade(ctx: BladeSpreadContext, val: ParsedExpr): string | null {
157
+ // The only shape `parseRecordIndexAccess` accepts is `IDENT[KEY]` with
158
+ // identifier object and index, so rebuild exactly that node from the
159
+ // carried tree via `ts.factory` — no source-text re-parse.
160
+ if (
161
+ val.kind !== 'index-access' ||
162
+ val.object.kind !== 'identifier' ||
163
+ val.index.kind !== 'identifier'
164
+ ) {
165
+ return null
166
+ }
167
+ const tsVal = ts.factory.createElementAccessExpression(
168
+ ts.factory.createIdentifier(val.object.name),
169
+ ts.factory.createIdentifier(val.index.name),
170
+ )
171
+ const parsed = parseRecordIndexAccess(tsVal, ctx.localConstants ?? [], ctx.propsParams)
172
+ if (!parsed) return null
173
+ const entries = parsed.entries.map(e => {
174
+ const mapVal =
175
+ e.value.kind === 'number' ? e.value.text : `'${escapeBladeSingleQuoted(e.value.text)}'`
176
+ return `${bladeHashKey(e.key)} => ${mapVal}`
177
+ })
178
+ return `[${entries.join(', ')}][${bladeVar(parsed.indexPropName)}]`
179
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * String-literal value lowering for the Blade template adapter.
3
+ *
4
+ * Ported from `packages/adapter-jinja/src/adapter/value/parsed-literal.ts`.
5
+ * Byte-identical logic (the source-text parsing here is language-agnostic;
6
+ * only the caller's escaping of the resolved value into a template literal
7
+ * differs, and that happens at the call site in `blade-adapter.ts`, not
8
+ * here).
9
+ */
10
+
11
+ import { evalStringArrayJoin, type TypeInfo } from '@barefootjs/jsx'
12
+
13
+ /**
14
+ * Parse a const initializer's source text. Returns the unescaped string value
15
+ * when the whole initializer is a single pure string literal — single/double
16
+ * quoted, or a no-substitution backtick template (no `${}`) — else `null`.
17
+ * Only such a value can be inlined byte-for-byte; template literals with
18
+ * interpolation, numbers, objects, and `Record<T,string>` maps are excluded.
19
+ */
20
+ export function parsePureStringLiteral(source: string): string | null {
21
+ let s = source.trim()
22
+ // Peel a single layer of wrapping parens.
23
+ while (s.startsWith('(') && s.endsWith(')')) s = s.slice(1, -1).trim()
24
+ const quote = s[0]
25
+ if ((quote === "'" || quote === '"') && s[s.length - 1] === quote) {
26
+ const body = s.slice(1, -1)
27
+ // Reject if an unescaped matching quote appears inside (not a single
28
+ // literal then).
29
+ if (containsUnescaped(body, quote)) return null
30
+ return unescapeStringLiteralBody(body)
31
+ }
32
+ if (quote === '`' && s[s.length - 1] === '`') {
33
+ const body = s.slice(1, -1)
34
+ if (body.includes('${')) return null
35
+ if (containsUnescaped(body, '`')) return null
36
+ return unescapeStringLiteralBody(body)
37
+ }
38
+ // `[<literals>].join(' ')` module consts (e.g. Switch's `trackStateClasses`)
39
+ // → inline the flattened string byte-for-byte. See `evalStringArrayJoin`.
40
+ return evalStringArrayJoin(source)
41
+ }
42
+
43
+ /** Whether `s` contains an unescaped occurrence of `ch`. */
44
+ export function containsUnescaped(s: string, ch: string): boolean {
45
+ for (let i = 0; i < s.length; i++) {
46
+ if (s[i] === '\\') { i++; continue }
47
+ if (s[i] === ch) return true
48
+ }
49
+ return false
50
+ }
51
+
52
+ /** Unescape a JS string-literal body's common escape sequences. */
53
+ export function unescapeStringLiteralBody(s: string): string {
54
+ return s.replace(/\\(.)/g, (_, c) => {
55
+ switch (c) {
56
+ case 'n': return '\n'
57
+ case 'r': return '\r'
58
+ case 't': return '\t'
59
+ case '0': return '\0'
60
+ default: return c
61
+ }
62
+ })
63
+ }
64
+
65
+ /** True when `type` is the `string` primitive. */
66
+ export function isStringTypeInfo(type: TypeInfo | undefined): boolean {
67
+ return type?.kind === 'primitive' && type.primitive === 'string'
68
+ }
69
+
70
+ /** True when `initialValue` is a bare string-literal expression. */
71
+ export function isBareStringLiteral(initialValue: string | undefined): boolean {
72
+ if (!initialValue) return false
73
+ const v = initialValue.trim()
74
+ return (v.startsWith("'") && v.endsWith("'")) || (v.startsWith('"') && v.endsWith('"'))
75
+ }
package/src/build.ts ADDED
@@ -0,0 +1,37 @@
1
+ // Blade build config factory for barefoot.config.ts
2
+
3
+ import type { BuildOptions } from '@barefootjs/jsx'
4
+ import { BladeAdapter } from './adapter/index.ts'
5
+ import type { BladeAdapterOptions } from './adapter/index.ts'
6
+
7
+ export interface BladeBuildOptions extends BuildOptions {
8
+ /** Adapter-specific options passed to BladeAdapter */
9
+ adapterOptions?: BladeAdapterOptions
10
+ }
11
+
12
+ /**
13
+ * Create a BarefootBuildConfig for Blade (PHP) template projects.
14
+ *
15
+ * Uses structural typing — does not import BarefootBuildConfig to avoid a
16
+ * circular dependency between @barefootjs/blade and @barefootjs/cli.
17
+ */
18
+ export function createConfig(options: BladeBuildOptions = {}) {
19
+ return {
20
+ adapter: new BladeAdapter(options.adapterOptions),
21
+ paths: options.paths,
22
+ components: options.components,
23
+ outDir: options.outDir,
24
+ minify: options.minify,
25
+ contentHash: options.contentHash,
26
+ externals: options.externals,
27
+ externalsBasePath: options.externalsBasePath,
28
+ bundleEntries: options.bundleEntries,
29
+ localImportPrefixes: options.localImportPrefixes,
30
+ outputLayout: options.outputLayout ?? {
31
+ templates: 'templates',
32
+ clientJs: 'client',
33
+ runtime: 'client',
34
+ },
35
+ postBuild: options.postBuild,
36
+ }
37
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Per-fixture build-time contracts for shapes the Blade adapter
3
+ * intentionally refuses to lower. Mirrors Jinja's set — the lowering
4
+ * gates are shared code paths in the ported adapter (BF103/BF104 are
5
+ * structural: cross-template child registration / destructure-loop-param
6
+ * limits that apply identically regardless of target template language).
7
+ * Consumed by this package's own conformance test (as `expectedDiagnostics`)
8
+ * and by `bf compat` (issue-URL attribution).
9
+ */
10
+
11
+ import type { ConformancePins } from '@barefootjs/jsx'
12
+
13
+ export const conformancePins: ConformancePins = {
14
+ // Sibling-imported child component in a loop body: emits a
15
+ // cross-template call needing separate registration. BF103 makes
16
+ // the requirement loud (same as Jinja).
17
+ 'static-array-children': [{ code: 'BF103', severity: 'error' }],
18
+ // TodoApp / TodoAppSSR import `TodoItem` from a sibling file and
19
+ // call it inside a keyed `.map`. Same BF103 (imported child in
20
+ // `.map`) as Jinja.
21
+ 'todo-app': [{ code: 'BF103', severity: 'error' }],
22
+ 'todo-app-ssr': [{ code: 'BF103', severity: 'error' }],
23
+ // The `([emoji, users]) => …` array-destructure param itself now lowers
24
+ // (#2087 Phase B — see the destructure comment below), but the loop
25
+ // ARRAY is a function-scope computed const (`const entries =
26
+ // Object.entries(props.reactions ?? {}).filter(...)`) that the adapter
27
+ // can't bind as a template variable — refused loudly with BF101 (same
28
+ // check and policy as Jinja / ERB) instead of silently iterating zero
29
+ // times over an unbound name.
30
+ 'static-array-from-props': [{ code: 'BF101', severity: 'error' }],
31
+ // Both BF103 (imported child) and BF101 (computed local-const loop
32
+ // array, as above) fire.
33
+ 'static-array-from-props-with-component': [
34
+ { code: 'BF103', severity: 'error' },
35
+ { code: 'BF101', severity: 'error' },
36
+ ],
37
+ // #2087 Phase B: every `.map()` destructure shape in the shared corpus
38
+ // now lowers on Blade via an `@php(...)` local built from the binding's
39
+ // structured `segments` path (`bladeLoopBindingAccessor` in
40
+ // `lib/blade-naming.ts`) — fixed bindings at any field/index depth
41
+ // (`destructure-array-index-in-map`, `destructure-nested-object-in-map`),
42
+ // array-rest via `$bf->slice` (`rest-destructure-array-in-map`,
43
+ // `rest-destructure-nested-in-map`), and object-rest via the new
44
+ // `$bf->omit` residual helper, read by member access
45
+ // (`rest-destructure-object-in-map`) or spread onto the element
46
+ // (`rest-destructure-object-spread-in-map`). No `expectedDiagnostics`
47
+ // pins remain for any of them — see `blade-adapter.ts`'s `renderLoop` for
48
+ // the still-refused shapes (bare-value rest use, `.filter().map()`
49
+ // chains, `__bf_`-prefixed names).
50
+ // (button/kbd graduated: the site/ui Button/Kbd `<Slot>` `{...props}` /
51
+ // `{...children.props}` component-spread now lowers via an
52
+ // `array_merge(...)` fold — see `blade-adapter.ts`'s `renderComponent` —
53
+ // instead of refusing with BF101, so these two no longer need a pin here.)
54
+ // (`tagged-template-classname` graduated by #2092 — the tag resolves
55
+ // through the interleave-tag catalogue and desugars to an untagged
56
+ // template literal, so it lowers like any other className template.)
57
+ // #2038: a filter predicate whose body contains a NESTED callback call
58
+ // (`t => !picked().some(p => …)` / `t => picked().find(p => …)`). Blade
59
+ // has no inline comprehension-with-nested-callback form usable from the
60
+ // evaluator-JSON `*_eval` payload mechanism (this adapter's ONLY
61
+ // higher-order-callback lowering path — see `blade-adapter.ts`'s file
62
+ // header, divergence 3), so the compiler is loud (BF101) instead of
63
+ // lossy, same as Jinja. The `/* @client */` twin
64
+ // (`filter-nested-callback-predicate-client`) has no pin here: it must
65
+ // render clean on every adapter, which asserts the suppression contract.
66
+ // https://github.com/piconic-ai/barefootjs/issues/2038
67
+ 'filter-nested-callback-predicate': [
68
+ { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
69
+ ],
70
+ 'filter-nested-find-predicate': [
71
+ { code: 'BF101', severity: 'error', issue: 'https://github.com/piconic-ai/barefootjs/issues/2038' },
72
+ ],
73
+ // NB: TOP-LEVEL `.find` / `.findIndex` / `.findLast` / `.findLastIndex`
74
+ // (text position) are NOT pinned here — like Jinja (unlike mojo, which
75
+ // refuses them), Blade lowers them to `$bf->find_eval` / `find_index_eval`
76
+ // / etc. via the same evaluator-JSON mechanism as `.filter` / `.every` /
77
+ // `.some`, so they render. Only the NESTED-in-a-predicate form above is
78
+ // refused (#2038).
79
+ // #2073 follow-up (same as Jinja): a function-reference `.map(format)`
80
+ // callback has no arrow body to serialize — not a CALLBACK_METHODS shape
81
+ // (`asCallbackMethodCall` requires an arrow argument) — so the shared
82
+ // `isSupported`'s `UNSUPPORTED_METHODS` gate refuses it with the generic
83
+ // "Expression not supported" BF101 rather than emitting a broken
84
+ // template.
85
+ 'array-map-function-reference': [{ code: 'BF101', severity: 'error' }],
86
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * BarefootJS Blade (PHP) Template Adapter
3
+ *
4
+ * Generates Blade template files (.blade.php) from BarefootJS IR.
5
+ */
6
+
7
+ export { BladeAdapter, bladeAdapter } from './adapter/index.ts'
8
+ export type { BladeAdapterOptions } from './adapter/index.ts'
9
+ export { conformancePins } from './conformance-pins.ts'