@barefootjs/jinja 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 (88) hide show
  1. package/README.md +66 -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 +84 -0
  5. package/dist/adapter/boolean-result.d.ts.map +1 -0
  6. package/dist/adapter/emit-context.d.ts +106 -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/expr/operand.d.ts +25 -0
  13. package/dist/adapter/expr/operand.d.ts.map +1 -0
  14. package/dist/adapter/index.d.ts +6 -0
  15. package/dist/adapter/index.d.ts.map +1 -0
  16. package/dist/adapter/index.js +189097 -0
  17. package/dist/adapter/jinja-adapter.d.ts +394 -0
  18. package/dist/adapter/jinja-adapter.d.ts.map +1 -0
  19. package/dist/adapter/lib/constants.d.ts +21 -0
  20. package/dist/adapter/lib/constants.d.ts.map +1 -0
  21. package/dist/adapter/lib/ir-scope.d.ts +50 -0
  22. package/dist/adapter/lib/ir-scope.d.ts.map +1 -0
  23. package/dist/adapter/lib/jinja-naming.d.ts +77 -0
  24. package/dist/adapter/lib/jinja-naming.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 +81 -0
  28. package/dist/adapter/memo/seed.d.ts.map +1 -0
  29. package/dist/adapter/props/prop-classes.d.ts +33 -0
  30. package/dist/adapter/props/prop-classes.d.ts.map +1 -0
  31. package/dist/adapter/spread/spread-codegen.d.ts +61 -0
  32. package/dist/adapter/spread/spread-codegen.d.ts.map +1 -0
  33. package/dist/adapter/value/parsed-literal.d.ts +28 -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 +189117 -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 +189118 -0
  43. package/package.json +66 -0
  44. package/python/VERSION +1 -0
  45. package/python/barefootjs/__init__.py +57 -0
  46. package/python/barefootjs/backend_jinja.py +125 -0
  47. package/python/barefootjs/evaluator.py +787 -0
  48. package/python/barefootjs/runtime.py +1334 -0
  49. package/python/barefootjs/search_params.py +89 -0
  50. package/python/pyproject.toml +32 -0
  51. package/python/tests/__init__.py +0 -0
  52. package/python/tests/test_eval_vectors.py +88 -0
  53. package/python/tests/test_evaluator.py +406 -0
  54. package/python/tests/test_helper_vectors.py +288 -0
  55. package/python/tests/test_omit.py +62 -0
  56. package/python/tests/test_props_attr.py +54 -0
  57. package/python/tests/test_query.py +41 -0
  58. package/python/tests/test_render.py +102 -0
  59. package/python/tests/test_render_child.py +96 -0
  60. package/python/tests/test_search_params.py +50 -0
  61. package/python/tests/test_spread_attrs.py +86 -0
  62. package/python/tests/test_template_primitives.py +347 -0
  63. package/python/tests/vector-divergences.json +42 -0
  64. package/src/__tests__/jinja-adapter-unit.test.ts +390 -0
  65. package/src/__tests__/jinja-adapter.test.ts +53 -0
  66. package/src/__tests__/jinja-counter.test.ts +62 -0
  67. package/src/__tests__/jinja-query-href.test.ts +99 -0
  68. package/src/__tests__/jinja-spread-attrs.test.ts +225 -0
  69. package/src/adapter/analysis/component-tree.ts +119 -0
  70. package/src/adapter/boolean-result.ts +176 -0
  71. package/src/adapter/emit-context.ts +118 -0
  72. package/src/adapter/expr/array-method.ts +346 -0
  73. package/src/adapter/expr/emitters.ts +608 -0
  74. package/src/adapter/expr/operand.ts +35 -0
  75. package/src/adapter/index.ts +6 -0
  76. package/src/adapter/jinja-adapter.ts +1747 -0
  77. package/src/adapter/lib/constants.ts +33 -0
  78. package/src/adapter/lib/ir-scope.ts +95 -0
  79. package/src/adapter/lib/jinja-naming.ts +114 -0
  80. package/src/adapter/lib/types.ts +30 -0
  81. package/src/adapter/memo/seed.ts +132 -0
  82. package/src/adapter/props/prop-classes.ts +65 -0
  83. package/src/adapter/spread/spread-codegen.ts +166 -0
  84. package/src/adapter/value/parsed-literal.ts +76 -0
  85. package/src/build.ts +37 -0
  86. package/src/conformance-pins.ts +101 -0
  87. package/src/index.ts +9 -0
  88. package/src/test-render.ts +714 -0
@@ -0,0 +1,76 @@
1
+ /**
2
+ * String-literal value lowering for the Jinja2 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 `jinja-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,37 @@
1
+ // Jinja2 build config factory for barefoot.config.ts
2
+
3
+ import type { BuildOptions } from '@barefootjs/jsx'
4
+ import { JinjaAdapter } from './adapter/index.ts'
5
+ import type { JinjaAdapterOptions } from './adapter/index.ts'
6
+
7
+ export interface JinjaBuildOptions extends BuildOptions {
8
+ /** Adapter-specific options passed to JinjaAdapter */
9
+ adapterOptions?: JinjaAdapterOptions
10
+ }
11
+
12
+ /**
13
+ * Create a BarefootBuildConfig for Jinja2 template projects.
14
+ *
15
+ * Uses structural typing — does not import BarefootBuildConfig to avoid a
16
+ * circular dependency between @barefootjs/jinja and @barefootjs/cli.
17
+ */
18
+ export function createConfig(options: JinjaBuildOptions = {}) {
19
+ return {
20
+ adapter: new JinjaAdapter(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,101 @@
1
+ /**
2
+ * Per-fixture build-time contracts for shapes the Jinja adapter
3
+ * intentionally refuses to lower. Mirrors xslate'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 xslate).
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 xslate.
21
+ 'todo-app': [{ code: 'BF103', severity: 'error' }],
22
+ 'todo-app-ssr': [{ code: 'BF103', severity: 'error' }],
23
+ // #2087 Phase A/B widened the destructure gate (`isLowerableLoopDestructure`)
24
+ // to admit array-index / nested-path fixed bindings, so the
25
+ // `([emoji, users]) => ...` / `([id, t]) => ...` params in these two
26
+ // fixtures no longer trip BF104 — the destructure itself now lowers
27
+ // cleanly to a native `{% set %}` accessor. But both fixtures' loop
28
+ // array is `entries`, a bare identifier bound to a function-scope local
29
+ // const with a NON-inlineable initializer
30
+ // (`Object.entries(props.x ?? {}).filter(...)`) — a pre-existing,
31
+ // orthogonal limitation this adapter has always had (it only ever
32
+ // INLINES a local const's value at its use site — numeric/string
33
+ // literal, or a static-record-literal lookup — never binds one as a
34
+ // `{% set %}` template local), just never reachable before because the
35
+ // narrower pre-#2087 gate refused the destructure param first. Left
36
+ // unhandled it would silently render an EMPTY list (Jinja's
37
+ // `ChainableUndefined` tolerates iterating an unbound name as zero
38
+ // iterations) instead of failing loudly, so `renderLoop` in
39
+ // `jinja-adapter.ts` now detects this shape and raises BF101 instead —
40
+ // see the "Loop array `<name>` is a local computed value" diagnostic.
41
+ // Fixing the underlying gap (computed-array-from-props as a loop
42
+ // source) is out of 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 / structured-path `.map()` callbacks (#2087 Phase B):
54
+ // `isLowerableLoopDestructure` now admits fixed bindings at any
55
+ // field/index depth, array-rest (`bf.slice`), and object-rest whose uses
56
+ // are member reads or a `{...rest}` spread onto an intrinsic element
57
+ // (`bf.omit` builds the TRUE residual dict). Each binding becomes a
58
+ // native `{% set %}` local off the per-item var — see
59
+ // `renderLoop`/`jinjaAccessorFromSegments` in `jinja-adapter.ts`. So
60
+ // `rest-destructure-object-in-map`, `rest-destructure-object-spread-in-map`,
61
+ // `rest-destructure-array-in-map`, `rest-destructure-nested-in-map`,
62
+ // `destructure-array-index-in-map`, and `destructure-nested-object-in-map`
63
+ // all render clean now — none of them are pinned here.
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 `jinja-adapter.ts`'s `renderComponent`
67
+ // — instead of refusing with BF101, so these two no longer need a pin
68
+ // 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 `jinja-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,9 @@
1
+ /**
2
+ * BarefootJS Jinja2 Template Adapter
3
+ *
4
+ * Generates Jinja2 template files (.jinja) from BarefootJS IR.
5
+ */
6
+
7
+ export { JinjaAdapter, jinjaAdapter } from './adapter/index.ts'
8
+ export type { JinjaAdapterOptions } from './adapter/index.ts'
9
+ export { conformancePins } from './conformance-pins.ts'