@rhinostone/swig 2.1.0 → 2.3.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 (101) hide show
  1. package/HISTORY.md +14 -0
  2. package/ROADMAP.md +11 -0
  3. package/bin/swig.js +272 -87
  4. package/dist/swig.js +120 -41
  5. package/dist/swig.min.js +2 -2
  6. package/dist/swig.min.js.map +1 -1
  7. package/lib/filters.js +23 -20
  8. package/lib/swig.js +13 -2
  9. package/lib/tags/import.js +42 -21
  10. package/lib/tags/include.js +4 -0
  11. package/package.json +3 -4
  12. package/.changes/unreleased/.gitkeep +0 -0
  13. package/.changes/v0.1.2.md +0 -6
  14. package/.changes/v0.1.3.md +0 -6
  15. package/.changes/v0.1.5.md +0 -11
  16. package/.changes/v0.1.6.md +0 -6
  17. package/.changes/v0.1.7.md +0 -7
  18. package/.changes/v0.1.8.md +0 -7
  19. package/.changes/v0.1.9.md +0 -7
  20. package/.changes/v0.10.0.md +0 -11
  21. package/.changes/v0.11.0.md +0 -15
  22. package/.changes/v0.11.1.md +0 -6
  23. package/.changes/v0.11.2.md +0 -6
  24. package/.changes/v0.12.0.md +0 -7
  25. package/.changes/v0.12.1.md +0 -11
  26. package/.changes/v0.13.0.md +0 -8
  27. package/.changes/v0.13.1.md +0 -8
  28. package/.changes/v0.13.2.md +0 -9
  29. package/.changes/v0.13.3.md +0 -5
  30. package/.changes/v0.13.4.md +0 -5
  31. package/.changes/v0.13.5.md +0 -4
  32. package/.changes/v0.14.0.md +0 -6
  33. package/.changes/v0.2.0.md +0 -7
  34. package/.changes/v0.2.1.md +0 -6
  35. package/.changes/v0.2.2.md +0 -6
  36. package/.changes/v0.2.3.md +0 -10
  37. package/.changes/v0.3.0.md +0 -6
  38. package/.changes/v0.4.0.md +0 -11
  39. package/.changes/v0.5.0.md +0 -11
  40. package/.changes/v0.6.0.md +0 -12
  41. package/.changes/v0.6.1.md +0 -6
  42. package/.changes/v0.7.0.md +0 -7
  43. package/.changes/v0.8.0.md +0 -18
  44. package/.changes/v0.9.0.md +0 -13
  45. package/.changes/v0.9.1.md +0 -6
  46. package/.changes/v0.9.2.md +0 -6
  47. package/.changes/v0.9.3.md +0 -6
  48. package/.changes/v0.9.4.md +0 -10
  49. package/.changes/v1.0.0-pre1.md +0 -22
  50. package/.changes/v1.0.0-pre2.md +0 -18
  51. package/.changes/v1.0.0-pre3.md +0 -7
  52. package/.changes/v1.0.0-rc1.md +0 -12
  53. package/.changes/v1.0.0-rc2.md +0 -10
  54. package/.changes/v1.0.0-rc3.md +0 -7
  55. package/.changes/v1.0.0.md +0 -7
  56. package/.changes/v1.1.0.md +0 -6
  57. package/.changes/v1.2.0.md +0 -10
  58. package/.changes/v1.2.1.md +0 -4
  59. package/.changes/v1.2.2.md +0 -4
  60. package/.changes/v1.3.0.md +0 -14
  61. package/.changes/v1.3.2.md +0 -6
  62. package/.changes/v1.4.0.md +0 -13
  63. package/.changes/v1.4.1.md +0 -5
  64. package/.changes/v1.4.2.md +0 -10
  65. package/.changes/v1.4.3.md +0 -4
  66. package/.changes/v1.4.4.md +0 -6
  67. package/.changes/v1.4.5.md +0 -10
  68. package/.changes/v1.4.6.md +0 -6
  69. package/.changes/v1.4.7.md +0 -8
  70. package/.changes/v1.5.0.md +0 -4
  71. package/.changes/v1.6.0.md +0 -4
  72. package/.changes/v2.0.0-alpha.1.md +0 -4
  73. package/.changes/v2.0.0-alpha.2.md +0 -4
  74. package/.changes/v2.0.0-alpha.4.md +0 -10
  75. package/.changes/v2.0.0-alpha.5.md +0 -16
  76. package/.changes/v2.0.0-alpha.6.md +0 -4
  77. package/.changes/v2.0.0-alpha.7.md +0 -4
  78. package/.changes/v2.0.0-alpha.8.md +0 -4
  79. package/.changes/v2.0.0.md +0 -6
  80. package/.changes/v2.0.1.md +0 -8
  81. package/.changes/v2.1.0.md +0 -8
  82. package/.playwright-mcp/console-2026-04-22T15-34-20-480Z.log +0 -2
  83. package/.playwright-mcp/console-2026-04-22T15-35-04-265Z.log +0 -11
  84. package/.playwright-mcp/console-2026-04-22T15-37-26-953Z.log +0 -1
  85. package/.playwright-mcp/console-2026-04-22T15-51-15-160Z.log +0 -148
  86. package/.playwright-mcp/console-2026-04-22T15-51-33-405Z.log +0 -74
  87. package/.playwright-mcp/console-2026-04-22T15-51-53-922Z.log +0 -74
  88. package/.playwright-mcp/console-2026-04-22T15-53-10-736Z.log +0 -74
  89. package/.playwright-mcp/console-2026-04-22T15-53-40-091Z.log +0 -883
  90. package/.playwright-mcp/console-2026-04-22T16-12-02-541Z.log +0 -74
  91. package/.playwright-mcp/console-2026-04-22T16-33-44-982Z.log +0 -13973
  92. package/.playwright-mcp/page-2026-04-22T15-34-24-524Z.yml +0 -1
  93. package/.playwright-mcp/page-2026-04-22T15-35-04-346Z.yml +0 -0
  94. package/.playwright-mcp/page-2026-04-22T15-37-27-039Z.yml +0 -5
  95. package/.playwright-mcp/page-2026-04-22T15-51-15-600Z.yml +0 -76
  96. package/.playwright-mcp/page-2026-04-22T15-51-33-605Z.yml +0 -0
  97. package/.playwright-mcp/page-2026-04-22T15-51-54-206Z.yml +0 -676
  98. package/.playwright-mcp/page-2026-04-22T15-53-11-277Z.yml +0 -632
  99. package/.playwright-mcp/page-2026-04-22T15-53-40-297Z.yml +0 -0
  100. package/.playwright-mcp/page-2026-04-22T16-12-02-855Z.yml +0 -0
  101. package/.playwright-mcp/page-2026-04-22T16-33-45-281Z.yml +0 -0
@@ -1,676 +0,0 @@
1
- - generic [ref=e2]:
2
- - region "Skip to main content":
3
- - link "Skip to main content" [ref=e3] [cursor=pointer]:
4
- - /url: "#__docusaurus_skipToContent_fallback"
5
- - navigation "Main" [ref=e4]:
6
- - generic [ref=e5]:
7
- - generic [ref=e6]:
8
- - link "Gina logo Gina" [ref=e7] [cursor=pointer]:
9
- - /url: /docs/
10
- - img "Gina logo" [ref=e9]
11
- - generic [ref=e10]: Gina
12
- - link "Docs" [ref=e11] [cursor=pointer]:
13
- - /url: /docs/intro
14
- - link "Tutorials" [ref=e12] [cursor=pointer]:
15
- - /url: /docs/tutorials
16
- - link "Swig" [ref=e13] [cursor=pointer]:
17
- - /url: /docs/swig/
18
- - link "Roadmap" [ref=e14] [cursor=pointer]:
19
- - /url: /docs/roadmap
20
- - generic [ref=e15]:
21
- - link "v0.3.6(opens in new tab)" [ref=e16] [cursor=pointer]:
22
- - /url: https://github.com/gina-io/gina/releases
23
- - text: v0.3.6
24
- - img "(opens in new tab)" [ref=e17]
25
- - link "GitHub(opens in new tab)" [ref=e19] [cursor=pointer]:
26
- - /url: https://github.com/gina-io/gina
27
- - text: GitHub
28
- - img "(opens in new tab)" [ref=e20]
29
- - button "Switch between dark and light mode (currently system mode)" [disabled] [ref=e23]:
30
- - img [ref=e24]
31
- - textbox "Search" [ref=e28]
32
- - generic [ref=e31]:
33
- - complementary [ref=e32]:
34
- - generic [ref=e33]:
35
- - generic [ref=e34]:
36
- - link "@rhinostone/swig v1.6.0" [ref=e35] [cursor=pointer]:
37
- - /url: https://www.npmjs.com/package/@rhinostone/swig
38
- - generic [ref=e36]: "@rhinostone/swig"
39
- - generic [ref=e37]: v1.6.0
40
- - link "gina-io/swig on GitHub" [ref=e38] [cursor=pointer]:
41
- - /url: https://github.com/gina-io/swig
42
- - img [ref=e39]
43
- - navigation "Docs sidebar" [ref=e42]:
44
- - list [ref=e43]:
45
- - listitem [ref=e44]:
46
- - link "Overview" [ref=e45] [cursor=pointer]:
47
- - /url: /docs/swig/
48
- - img [ref=e47]
49
- - generic "Overview" [ref=e50]
50
- - listitem [ref=e51]:
51
- - link "Getting Started" [ref=e52] [cursor=pointer]:
52
- - /url: /docs/swig/getting-started
53
- - img [ref=e54]
54
- - generic "Getting Started" [ref=e59]
55
- - listitem [ref=e60]:
56
- - link "Template Syntax" [ref=e61] [cursor=pointer]:
57
- - /url: /docs/swig/syntax
58
- - img [ref=e63]
59
- - generic "Template Syntax" [ref=e66]
60
- - listitem [ref=e67]:
61
- - link "Tags" [ref=e68] [cursor=pointer]:
62
- - /url: /docs/swig/tags
63
- - img [ref=e70]
64
- - generic "Tags" [ref=e72]
65
- - listitem [ref=e73]:
66
- - link "Filters" [ref=e74] [cursor=pointer]:
67
- - /url: /docs/swig/filters
68
- - img [ref=e76]
69
- - generic "Filters" [ref=e78]
70
- - listitem [ref=e79]:
71
- - link "API" [ref=e80] [cursor=pointer]:
72
- - /url: /docs/swig/api
73
- - img [ref=e82]
74
- - generic "API" [ref=e85]
75
- - listitem [ref=e86]:
76
- - link "Loaders" [ref=e87] [cursor=pointer]:
77
- - /url: /docs/swig/loaders
78
- - img [ref=e89]
79
- - generic "Loaders" [ref=e93]
80
- - listitem [ref=e94]:
81
- - link "Extending Swig" [ref=e95] [cursor=pointer]:
82
- - /url: /docs/swig/extending
83
- - img [ref=e97]
84
- - generic "Extending Swig" [ref=e99]
85
- - listitem [ref=e100]:
86
- - link "CLI" [ref=e101] [cursor=pointer]:
87
- - /url: /docs/swig/cli
88
- - img [ref=e103]
89
- - generic "CLI" [ref=e105]
90
- - listitem [ref=e106]:
91
- - link "Browser Usage" [ref=e107] [cursor=pointer]:
92
- - /url: /docs/swig/browser
93
- - img [ref=e109]
94
- - generic "Browser Usage" [ref=e111]
95
- - listitem [ref=e112]:
96
- - link "Security" [ref=e113] [cursor=pointer]:
97
- - /url: /docs/swig/security
98
- - img [ref=e115]
99
- - generic "Security" [ref=e117]
100
- - listitem [ref=e118]:
101
- - generic [ref=e119]:
102
- - link "Twig Frontend" [ref=e120] [cursor=pointer]:
103
- - /url: /docs/swig/twig/
104
- - generic "Twig Frontend" [ref=e121]
105
- - button "Collapse sidebar category 'Twig Frontend'" [expanded] [ref=e122] [cursor=pointer]
106
- - list [ref=e123]:
107
- - listitem [ref=e124]:
108
- - link "Parity" [ref=e125] [cursor=pointer]:
109
- - /url: /docs/swig/twig/parity
110
- - generic "Parity" [ref=e126]
111
- - listitem [ref=e127]:
112
- - link "Non-Goals" [ref=e128] [cursor=pointer]:
113
- - /url: /docs/swig/twig/non-goals
114
- - generic "Non-Goals" [ref=e129]
115
- - listitem [ref=e130]:
116
- - link "From upstream Twig" [ref=e131] [cursor=pointer]:
117
- - /url: /docs/swig/twig/migration
118
- - generic "From upstream Twig" [ref=e132]
119
- - listitem [ref=e133]:
120
- - link "Browser Usage" [ref=e134] [cursor=pointer]:
121
- - /url: /docs/swig/twig/browser
122
- - img [ref=e136]
123
- - generic "Browser Usage" [ref=e138]
124
- - listitem [ref=e139]:
125
- - link "Migration Guide" [ref=e140] [cursor=pointer]:
126
- - /url: /docs/swig/migration
127
- - img [ref=e142]
128
- - generic "Migration Guide" [ref=e144]
129
- - main [ref=e145]:
130
- - generic [ref=e147]:
131
- - generic [ref=e149]:
132
- - article [ref=e150]:
133
- - navigation "Breadcrumbs" [ref=e151]:
134
- - list [ref=e152]:
135
- - listitem [ref=e153]:
136
- - link "Home page" [ref=e154] [cursor=pointer]:
137
- - /url: /docs/
138
- - img [ref=e155]
139
- - listitem [ref=e157]:
140
- - link "Twig Frontend" [ref=e158] [cursor=pointer]:
141
- - /url: /docs/swig/twig/
142
- - listitem [ref=e159]:
143
- - generic [ref=e160]: Non-Goals
144
- - generic [ref=e161]:
145
- - heading "Twig Non-Goals" [level=1] [ref=e163]
146
- - generic [ref=e164]:
147
- - generic [ref=e165]:
148
- - generic [ref=e166]:
149
- - img [ref=e167]
150
- - text: 6 min read
151
- - generic [ref=e170]: Intermediate
152
- - generic [ref=e171]:
153
- - generic [ref=e172]: Prerequisites
154
- - list [ref=e173]:
155
- - listitem [ref=e174]:
156
- - link "Overview" [ref=e175] [cursor=pointer]:
157
- - /url: /docs/swig/twig/
158
- - listitem [ref=e176]:
159
- - link "Parity" [ref=e177] [cursor=pointer]:
160
- - /url: /docs/swig/twig/parity
161
- - listitem [ref=e178]:
162
- - link "Security — the parser is the boundary" [ref=e179] [cursor=pointer]:
163
- - /url: /docs/swig/security#the-parser-is-the-boundary
164
- - paragraph [ref=e180]:
165
- - code [ref=e181]: "@rhinostone/swig-twig"
166
- - text: is a
167
- - strong [ref=e182]: strict subset
168
- - text: of upstream PHP Twig. Syntax outside the subset throws at parse time rather than silently producing a template that behaves differently from upstream. This page enumerates each rejection, the error message produced, and the rationale.
169
- - paragraph [ref=e183]: "The rejections fall into three buckets:"
170
- - list [ref=e184]:
171
- - listitem [ref=e185]:
172
- - strong [ref=e186]: Sandbox / extension tags
173
- - text: that rely on runtime features swig-core does not expose.
174
- - listitem [ref=e187]:
175
- - strong [ref=e188]: Deferred features
176
- - text: that will likely ship later but need dedicated IR design work.
177
- - listitem [ref=e189]:
178
- - strong [ref=e190]: Intentional security rejections
179
- - text: where silent acceptance would weaken the CVE-2023-25345 guard perimeter.
180
- - heading "Unsupported tagsDirect link to Unsupported tags" [level=2] [ref=e191]:
181
- - text: Unsupported tags
182
- - link "Direct link to Unsupported tags" [ref=e192] [cursor=pointer]:
183
- - /url: "#unsupported-tags"
184
- - text: "#"
185
- - paragraph [ref=e193]:
186
- - text: All of the following throw a generic
187
- - code [ref=e194]: Unexpected tag "<name>"
188
- - text: "error at parse time. They split into two categories:"
189
- - list [ref=e195]:
190
- - listitem [ref=e196]:
191
- - strong [ref=e197]: Won't support (security).
192
- - text: Threat-model calls; unlikely to change.
193
- - listitem [ref=e198]:
194
- - strong [ref=e199]: Deferred.
195
- - text: Design work awaiting demand —
196
- - link "open an issue" [ref=e200] [cursor=pointer]:
197
- - /url: https://github.com/gina-io/swig/issues
198
- - text: if you hit one.
199
- - heading "Won't support (security)Direct link to Won't support (security)" [level=3] [ref=e201]:
200
- - text: Won't support (security)
201
- - link "Direct link to Won't support (security)" [ref=e202] [cursor=pointer]:
202
- - /url: "#wont-support-security"
203
- - text: "#"
204
- - table [ref=e203]:
205
- - rowgroup [ref=e204]:
206
- - row "Tag Upstream purpose Why rejected" [ref=e205]:
207
- - columnheader "Tag" [ref=e206]
208
- - columnheader "Upstream purpose" [ref=e207]
209
- - columnheader "Why rejected" [ref=e208]
210
- - rowgroup [ref=e209]:
211
- - 'row "{% sandbox %} Restrict the template runtime (whitelist tags, filters, functions). swig-twig''s threat model declares template source trusted (see Security — the parser is the boundary). Inverting that requires closing runtime bracket access (foo[varname] where varname resolves to \"__proto__\" — a known unclosable gap), re-auditing every .safe=true filter as attacker-reachable, and accepting that new Function(...) compiles the attacker-controlled source. A partial sandbox that users trust is strictly worse than none." [ref=e210]':
212
- - 'cell "{% sandbox %}" [ref=e211]':
213
- - code [ref=e212]: "{% sandbox %}"
214
- - cell "Restrict the template runtime (whitelist tags, filters, functions)." [ref=e213]
215
- - cell "swig-twig's threat model declares template source trusted (see Security — the parser is the boundary). Inverting that requires closing runtime bracket access (foo[varname] where varname resolves to \"__proto__\" — a known unclosable gap), re-auditing every .safe=true filter as attacker-reachable, and accepting that new Function(...) compiles the attacker-controlled source. A partial sandbox that users trust is strictly worse than none." [ref=e214]:
216
- - text: swig-twig's threat model declares template source trusted (see
217
- - link "Security — the parser is the boundary" [ref=e215] [cursor=pointer]:
218
- - /url: /docs/swig/security#the-parser-is-the-boundary
219
- - text: ). Inverting that requires closing runtime bracket access (
220
- - code [ref=e216]: foo[varname]
221
- - text: where varname resolves to
222
- - code [ref=e217]: "\"__proto__\""
223
- - text: — a
224
- - link "known unclosable gap" [ref=e218] [cursor=pointer]:
225
- - /url: /docs/swig/security#what-the-guards-do-not-protect-against
226
- - text: ), re-auditing every
227
- - code [ref=e219]: .safe=true
228
- - text: filter as attacker-reachable, and accepting that
229
- - code [ref=e220]: new Function(...)
230
- - text: compiles the attacker-controlled source. A partial sandbox that users trust is strictly worse than none.
231
- - 'row "{% do %} Evaluate an expression for its side effects. Every token-swallowing tag is a new _dangerousProps audit site, duplicated across frontend and backend (see CVE-2023-25345 Phase 2). Small surface widening, for pure sugar over {{ foo.method() }} — autoescape already discards the return value." [ref=e221]':
232
- - 'cell "{% do %}" [ref=e222]':
233
- - code [ref=e223]: "{% do %}"
234
- - cell "Evaluate an expression for its side effects." [ref=e224]
235
- - 'cell "Every token-swallowing tag is a new _dangerousProps audit site, duplicated across frontend and backend (see CVE-2023-25345 Phase 2). Small surface widening, for pure sugar over {{ foo.method() }} — autoescape already discards the return value." [ref=e225]':
236
- - text: Every token-swallowing tag is a new
237
- - code [ref=e226]: _dangerousProps
238
- - text: audit site, duplicated across frontend and backend (see
239
- - link "CVE-2023-25345 Phase 2" [ref=e227] [cursor=pointer]:
240
- - /url: /docs/swig/security#cve-2023-25345--arbitrary-code-execution-via-__proto__
241
- - text: ). Small surface widening, for pure sugar over
242
- - code [ref=e228]: "{{ foo.method() }}"
243
- - text: — autoescape already discards the return value.
244
- - heading "DeferredDirect link to Deferred" [level=3] [ref=e229]:
245
- - text: Deferred
246
- - link "Direct link to Deferred" [ref=e230] [cursor=pointer]:
247
- - /url: "#deferred"
248
- - text: "#"
249
- - table [ref=e231]:
250
- - rowgroup [ref=e232]:
251
- - row "Tag Upstream purpose Why not yet" [ref=e233]:
252
- - columnheader "Tag" [ref=e234]
253
- - columnheader "Upstream purpose" [ref=e235]
254
- - columnheader "Why not yet" [ref=e236]
255
- - rowgroup [ref=e237]:
256
- - 'row "{% embed %} Include with block-override. Compound of include + block remapping. Most commonly requested of the three — likely to ship in a later minor if demand shows up." [ref=e238]':
257
- - 'cell "{% embed %}" [ref=e239]':
258
- - code [ref=e240]: "{% embed %}"
259
- - cell "Include with block-override." [ref=e241]
260
- - cell "Compound of include + block remapping. Most commonly requested of the three — likely to ship in a later minor if demand shows up." [ref=e242]:
261
- - text: Compound of
262
- - code [ref=e243]: include
263
- - text: + block remapping. Most commonly requested of the three — likely to ship in a later minor if demand shows up.
264
- - 'row "{% use %} Pull blocks in from a \"trait\" template without inheriting it. Block composition is expressible via extends + include today. Needs a dedicated IR node." [ref=e244]':
265
- - 'cell "{% use %}" [ref=e245]':
266
- - code [ref=e246]: "{% use %}"
267
- - cell "Pull blocks in from a \"trait\" template without inheriting it." [ref=e247]
268
- - cell "Block composition is expressible via extends + include today. Needs a dedicated IR node." [ref=e248]:
269
- - text: Block composition is expressible via
270
- - code [ref=e249]: extends
271
- - text: +
272
- - code [ref=e250]: include
273
- - text: today. Needs a dedicated IR node.
274
- - 'row "{% deprecated %} Emit a deprecation warning from within a template. Trivial to build; low demand in swig-twig''s likely audience." [ref=e251]':
275
- - 'cell "{% deprecated %}" [ref=e252]':
276
- - code [ref=e253]: "{% deprecated %}"
277
- - cell "Emit a deprecation warning from within a template." [ref=e254]
278
- - cell "Trivial to build; low demand in swig-twig's likely audience." [ref=e255]
279
- - paragraph [ref=e256]: "If you need one of these, either:"
280
- - list [ref=e257]:
281
- - listitem [ref=e258]:
282
- - text: Express the same intent with supported tags (e.g.
283
- - code [ref=e259]: extends
284
- - text: +
285
- - code [ref=e260]: block
286
- - text: for trait-style composition).
287
- - listitem [ref=e261]:
288
- - text: Register a
289
- - link "custom tag" [ref=e262] [cursor=pointer]:
290
- - /url: /docs/swig/extending#custom-tags
291
- - text: scoped to your application.
292
- - listitem [ref=e263]:
293
- - link "Open an issue" [ref=e264] [cursor=pointer]:
294
- - /url: https://github.com/gina-io/swig/issues
295
- - text: with a concrete use case — especially for the "Deferred" rows.
296
- - heading "Macro kwargsDirect link to Macro kwargs" [level=2] [ref=e265]:
297
- - text: Macro kwargs
298
- - link "Direct link to Macro kwargs" [ref=e266] [cursor=pointer]:
299
- - /url: "#macro-kwargs"
300
- - text: "#"
301
- - paragraph [ref=e267]: "Upstream Twig supports positional and keyword macro calls:"
302
- - code [ref=e271]:
303
- - generic [ref=e272]: "{% macro field(name, value=null, class=\"\") %}…{% endmacro %}"
304
- - generic [ref=e273]: "{{ field(\"email\", value=user.email, class=\"required\") }}"
305
- - paragraph [ref=e274]:
306
- - text: swig-twig accepts the positional form —
307
- - code [ref=e275]: "{{ field(\"email\", user.email, \"required\") }}"
308
- - text: — but
309
- - strong [ref=e276]: rejects the keyword-argument form
310
- - text: . Lowering kwargs needs a per-macro parameter map in the IR, plus a runtime dispatcher in
311
- - code [ref=e277]: swig-core
312
- - text: . That landed on the Phase 4 target list but slipped past alpha.5.
313
- - paragraph [ref=e278]: "Workarounds:"
314
- - list [ref=e279]:
315
- - listitem [ref=e280]:
316
- - text: "Pass an object literal and destructure inside the macro:"
317
- - code [ref=e284]:
318
- - generic [ref=e285]: "{% macro field(args) %}{{ args.name }} — {{ args.class ?? \"\" }}{% endmacro %}"
319
- - generic [ref=e286]: "{{ field({ name: \"email\", class: \"required\" }) }}"
320
- - listitem [ref=e287]: Order positional parameters from most-commonly-passed to least-commonly-passed so defaults "fall off the right."
321
- - 'heading "Dynamic {% extends %} and {% from %}Direct link to dynamic--extends--and--from-" [level=2] [ref=e288]':
322
- - text: Dynamic
323
- - code [ref=e289]: "{% extends %}"
324
- - text: and
325
- - code [ref=e290]: "{% from %}"
326
- - link "Direct link to dynamic--extends--and--from-" [ref=e291] [cursor=pointer]:
327
- - /url: "#dynamic--extends--and--from-"
328
- - text: "#"
329
- - paragraph [ref=e292]:
330
- - text: Both tags require the path to be a
331
- - strong [ref=e293]: string literal
332
- - text: ". Expressions throw:"
333
- - table [ref=e294]:
334
- - rowgroup [ref=e295]:
335
- - row "Shape Accepted? Error" [ref=e296]:
336
- - columnheader "Shape" [ref=e297]
337
- - columnheader "Accepted?" [ref=e298]
338
- - columnheader "Error" [ref=e299]
339
- - rowgroup [ref=e300]:
340
- - 'row "{% extends \"base.twig\" %} Yes —" [ref=e301]':
341
- - 'cell "{% extends \"base.twig\" %}" [ref=e302]':
342
- - code [ref=e303]: "{% extends \"base.twig\" %}"
343
- - cell "Yes" [ref=e304]
344
- - cell "—" [ref=e305]
345
- - 'row "{% extends parent_template %} No Dynamic \"extends\" is not supported — parent path must be a string literal" [ref=e306]':
346
- - 'cell "{% extends parent_template %}" [ref=e307]':
347
- - code [ref=e308]: "{% extends parent_template %}"
348
- - cell "No" [ref=e309]
349
- - cell "Dynamic \"extends\" is not supported — parent path must be a string literal" [ref=e310]:
350
- - code [ref=e311]: Dynamic "extends" is not supported — parent path must be a string literal
351
- - 'row "{% extends cond ? \"a.twig\" : \"b.twig\" %} No Same as above." [ref=e312]':
352
- - 'cell "{% extends cond ? \"a.twig\" : \"b.twig\" %}" [ref=e313]':
353
- - code [ref=e314]: "{% extends cond ? \"a.twig\" : \"b.twig\" %}"
354
- - cell "No" [ref=e315]
355
- - cell "Same as above." [ref=e316]
356
- - 'row "{% from \"macros.twig\" import foo %} Yes —" [ref=e317]':
357
- - 'cell "{% from \"macros.twig\" import foo %}" [ref=e318]':
358
- - code [ref=e319]: "{% from \"macros.twig\" import foo %}"
359
- - cell "Yes" [ref=e320]
360
- - cell "—" [ref=e321]
361
- - 'row "{% from dynamic_path import foo %} No Dynamic \"from\" is not supported — path must be a string literal" [ref=e322]':
362
- - 'cell "{% from dynamic_path import foo %}" [ref=e323]':
363
- - code [ref=e324]: "{% from dynamic_path import foo %}"
364
- - cell "No" [ref=e325]
365
- - cell "Dynamic \"from\" is not supported — path must be a string literal" [ref=e326]:
366
- - code [ref=e327]: Dynamic "from" is not supported — path must be a string literal
367
- - paragraph [ref=e328]:
368
- - text: "Rationale: swig's"
369
- - code [ref=e329]: getParents()
370
- - text: walks the inheritance chain at parse time so it can cache compiled templates by resolved filename. A dynamic parent would either break that caching (recompile on every render) or require a runtime loader path that none of the built-in loaders support today. The string-literal requirement keeps compiled output deterministic and lets the shared cache work.
371
- - paragraph [ref=e330]:
372
- - text: "Workaround:"
373
- - strong [ref=e331]: dispatch in the caller
374
- - text: and render the selected template directly.
375
- - code [ref=e335]:
376
- - generic [ref=e336]: "var template = (flavor === 'admin') ? 'page_admin.twig' : 'page_public.twig';"
377
- - generic [ref=e337]: "twig.renderFile(template, { /* locals */ });"
378
- - paragraph [ref=e338]:
379
- - text: Wrapping
380
- - code [ref=e339]: "{% extends %}"
381
- - text: in
382
- - code [ref=e340]: "{% if %}"
383
- - text: blocks does not work — the parent link is resolved at parse time, so whichever
384
- - code [ref=e341]: extends
385
- - text: the parser sees last wins regardless of the runtime condition. This applies whether you use the (currently
386
- - link "deferred" [ref=e342] [cursor=pointer]:
387
- - /url: "#conditional-branches-deferred"
388
- - text: )
389
- - code [ref=e343]: "{% if %}…{% else %}…{% endif %}"
390
- - text: form or paired
391
- - code [ref=e344]: "{% if %}"
392
- - text: blocks. Dispatch in the caller is the only working path.
393
- - paragraph [ref=e345]:
394
- - code [ref=e346]: include
395
- - text: is not affected — its path can be any expression.
396
- - heading "Bracket-notation setDirect link to bracket-notation-set" [level=2] [ref=e347]:
397
- - text: Bracket-notation
398
- - code [ref=e348]: set
399
- - link "Direct link to bracket-notation-set" [ref=e349] [cursor=pointer]:
400
- - /url: "#bracket-notation-set"
401
- - text: "#"
402
- - paragraph [ref=e350]: "Dot-path LHS is supported; bracket-notation LHS throws:"
403
- - table [ref=e351]:
404
- - rowgroup [ref=e352]:
405
- - row "Shape Accepted? Error" [ref=e353]:
406
- - columnheader "Shape" [ref=e354]
407
- - columnheader "Accepted?" [ref=e355]
408
- - columnheader "Error" [ref=e356]
409
- - rowgroup [ref=e357]:
410
- - 'row "{% set foo = 1 %} Yes —" [ref=e358]':
411
- - 'cell "{% set foo = 1 %}" [ref=e359]':
412
- - code [ref=e360]: "{% set foo = 1 %}"
413
- - cell "Yes" [ref=e361]
414
- - cell "—" [ref=e362]
415
- - 'row "{% set foo.bar = 1 %} Yes —" [ref=e363]':
416
- - 'cell "{% set foo.bar = 1 %}" [ref=e364]':
417
- - code [ref=e365]: "{% set foo.bar = 1 %}"
418
- - cell "Yes" [ref=e366]
419
- - cell "—" [ref=e367]
420
- - 'row "{% set foo[\"bar\"] = 1 %} No Bracket-notation assignment is not supported in \"set\" (use dot-path notation)" [ref=e368]':
421
- - 'cell "{% set foo[\"bar\"] = 1 %}" [ref=e369]':
422
- - code [ref=e370]: "{% set foo[\"bar\"] = 1 %}"
423
- - cell "No" [ref=e371]
424
- - cell "Bracket-notation assignment is not supported in \"set\" (use dot-path notation)" [ref=e372]:
425
- - code [ref=e373]: Bracket-notation assignment is not supported in "set" (use dot-path notation)
426
- - 'row "{% set foo[dynamic_key] = 1 %} No Same as above." [ref=e374]':
427
- - 'cell "{% set foo[dynamic_key] = 1 %}" [ref=e375]':
428
- - code [ref=e376]: "{% set foo[dynamic_key] = 1 %}"
429
- - cell "No" [ref=e377]
430
- - cell "Same as above." [ref=e378]
431
- - paragraph [ref=e379]:
432
- - text: "Rationale: bracket-notation writes at runtime can reach through the prototype chain ("
433
- - code [ref=e380]: foo["__proto__"]["x"] = …
434
- - text: ) in ways that the parse-time
435
- - code [ref=e381]: _dangerousProps
436
- - text: check cannot fully enclose. Dot-path writes are statically analysable — each segment is a lexer-validated identifier — and the native swig frontend already went through this audit during
437
- - link "CVE-2023-25345 Phase 2" [ref=e382] [cursor=pointer]:
438
- - /url: /docs/swig/security#cve-2023-25345--arbitrary-code-execution-via-__proto__
439
- - text: . swig-twig inherits the same perimeter.
440
- - heading "Conditional branches (deferred)Direct link to Conditional branches (deferred)" [level=2] [ref=e383]:
441
- - text: Conditional branches (deferred)
442
- - link "Direct link to Conditional branches (deferred)" [ref=e384] [cursor=pointer]:
443
- - /url: "#conditional-branches-deferred"
444
- - text: "#"
445
- - paragraph [ref=e385]:
446
- - text: Upstream Twig supports multi-branch conditionals —
447
- - code [ref=e386]: "{% if %} … {% elseif %} … {% else %} … {% endif %}"
448
- - text: — and an empty-fallback form on
449
- - code [ref=e387]: "{% for %}"
450
- - text: —
451
- - code [ref=e388]: "{% for %} … {% else %} … {% endfor %}"
452
- - text: (runs when the iterable is empty). swig-twig alpha.8 ships only the single-branch form.
453
- - table [ref=e389]:
454
- - rowgroup [ref=e390]:
455
- - row "Shape Accepted? Error" [ref=e391]:
456
- - columnheader "Shape" [ref=e392]
457
- - columnheader "Accepted?" [ref=e393]
458
- - columnheader "Error" [ref=e394]
459
- - rowgroup [ref=e395]:
460
- - 'row "{% if x %}…{% endif %} Yes —" [ref=e396]':
461
- - 'cell "{% if x %}…{% endif %}" [ref=e397]':
462
- - code [ref=e398]: "{% if x %}…{% endif %}"
463
- - cell "Yes" [ref=e399]
464
- - cell "—" [ref=e400]
465
- - 'row "{% if x %}…{% else %}…{% endif %} No Unexpected tag \"else\"" [ref=e401]':
466
- - 'cell "{% if x %}…{% else %}…{% endif %}" [ref=e402]':
467
- - code [ref=e403]: "{% if x %}…{% else %}…{% endif %}"
468
- - cell "No" [ref=e404]
469
- - cell "Unexpected tag \"else\"" [ref=e405]:
470
- - code [ref=e406]: Unexpected tag "else"
471
- - 'row "{% if x %}…{% elseif y %}…{% endif %} No Unexpected tag \"elseif\"" [ref=e407]':
472
- - 'cell "{% if x %}…{% elseif y %}…{% endif %}" [ref=e408]':
473
- - code [ref=e409]: "{% if x %}…{% elseif y %}…{% endif %}"
474
- - cell "No" [ref=e410]
475
- - cell "Unexpected tag \"elseif\"" [ref=e411]:
476
- - code [ref=e412]: Unexpected tag "elseif"
477
- - 'row "{% for x in items %}…{% endfor %} Yes —" [ref=e413]':
478
- - 'cell "{% for x in items %}…{% endfor %}" [ref=e414]':
479
- - code [ref=e415]: "{% for x in items %}…{% endfor %}"
480
- - cell "Yes" [ref=e416]
481
- - cell "—" [ref=e417]
482
- - 'row "{% for x in items %}…{% else %}…{% endfor %} No Unexpected tag \"else\"" [ref=e418]':
483
- - 'cell "{% for x in items %}…{% else %}…{% endfor %}" [ref=e419]':
484
- - code [ref=e420]: "{% for x in items %}…{% else %}…{% endfor %}"
485
- - cell "No" [ref=e421]
486
- - cell "Unexpected tag \"else\"" [ref=e422]:
487
- - code [ref=e423]: Unexpected tag "else"
488
- - paragraph [ref=e424]:
489
- - text: "Rationale: the single-branch shape shipped first to unblock the rest of the Twig parser surface. Multi-branch lowering needs a dedicated branch-chain IR node plus the frontend handler that consumes"
490
- - code [ref=e425]: else
491
- - text: /
492
- - code [ref=e426]: elseif
493
- - text: tokens without pushing them on the open-tag stack. Not blocking
494
- - code [ref=e427]: 2.0.0
495
- - text: stable.
496
- - link "Open an issue" [ref=e428] [cursor=pointer]:
497
- - /url: https://github.com/gina-io/swig/issues
498
- - text: with a concrete use case to prioritise.
499
- - paragraph [ref=e429]: "Workarounds that parse under alpha.8:"
500
- - list [ref=e430]:
501
- - listitem [ref=e431]:
502
- - strong [ref=e432]:
503
- - code [ref=e433]: "{% if %} / {% else %}"
504
- - text: → two
505
- - code [ref=e434]: if
506
- - text: blocks with
507
- - code [ref=e435]: not
508
- - text: ":"
509
- - code [ref=e439]:
510
- - generic [ref=e440]: "{% if x %}yes{% endif %}"
511
- - generic [ref=e441]: "{% if not x %}no{% endif %}"
512
- - listitem [ref=e442]:
513
- - strong [ref=e443]:
514
- - code [ref=e444]: "{% if %} / {% elseif %} / {% else %}"
515
- - text: → nested
516
- - code [ref=e445]: if
517
- - text: ":"
518
- - code [ref=e449]:
519
- - generic [ref=e450]: "{% if x %}a{% endif %}"
520
- - generic [ref=e451]: "{% if not x %}"
521
- - generic [ref=e452]: "{% if y %}b{% endif %}"
522
- - generic [ref=e453]: "{% if not y %}c{% endif %}"
523
- - generic [ref=e454]: "{% endif %}"
524
- - text: Uglier than the native form but semantically equivalent.
525
- - listitem [ref=e455]:
526
- - strong [ref=e456]:
527
- - code [ref=e457]: "{% for … %}{% else %}…{% endfor %}"
528
- - text: → guard with
529
- - code [ref=e458]: "{% if items|length %}"
530
- - text: "before the loop:"
531
- - code [ref=e462]:
532
- - generic [ref=e463]: "{% if items|length %}"
533
- - generic [ref=e464]: "{% for item in items %}{{ item }}{% endfor %}"
534
- - generic [ref=e465]: "{% endif %}"
535
- - generic [ref=e466]: "{% if not items|length %}empty{% endif %}"
536
- - heading "Other known gapsDirect link to Other known gaps" [level=2] [ref=e467]:
537
- - text: Other known gaps
538
- - link "Direct link to Other known gaps" [ref=e468] [cursor=pointer]:
539
- - /url: "#other-known-gaps"
540
- - text: "#"
541
- - paragraph [ref=e469]: "These are tracked but not yet implemented:"
542
- - list [ref=e470]:
543
- - listitem [ref=e471]:
544
- - strong [ref=e472]:
545
- - code [ref=e473]: date_modify
546
- - text: filter.
547
- - text: Relies on DateTime-object mutation; deferred.
548
- - listitem [ref=e474]:
549
- - strong [ref=e475]:
550
- - text: Locale-aware
551
- - code [ref=e476]: date
552
- - text: /
553
- - code [ref=e477]: number_format
554
- - text: .
555
- - text: swig-twig's
556
- - code [ref=e478]: date
557
- - text: uses the shared PHP-style formatter from
558
- - code [ref=e479]: "@rhinostone/swig-core"
559
- - text: ; locale rules (weekday names, currency formats) are not plumbed through yet.
560
- - listitem [ref=e480]:
561
- - strong [ref=e481]: Runtime bracket-access guard.
562
- - code [ref=e482]: "{{ foo[varname] }}"
563
- - text: with
564
- - code [ref=e483]: varname
565
- - text: resolving to
566
- - code [ref=e484]: "\"__proto__\""
567
- - text: at runtime is not blocked. Documented at the engine level — see
568
- - link "Security — what the guards do NOT protect against" [ref=e485] [cursor=pointer]:
569
- - /url: /docs/swig/security#what-the-guards-do-not-protect-against
570
- - text: .
571
- - heading "Reporting a rejection that feels wrongDirect link to Reporting a rejection that feels wrong" [level=2] [ref=e486]:
572
- - text: Reporting a rejection that feels wrong
573
- - link "Direct link to Reporting a rejection that feels wrong" [ref=e487] [cursor=pointer]:
574
- - /url: "#reporting-a-rejection-that-feels-wrong"
575
- - text: "#"
576
- - paragraph [ref=e488]:
577
- - text: If the rejection you hit isn't listed here, please
578
- - link "open an issue on gina-io/swig" [ref=e489] [cursor=pointer]:
579
- - /url: https://github.com/gina-io/swig/issues
580
- - text: open an issue on
581
- - code [ref=e490]: gina-io/swig
582
- - text: with a minimal reproducer — we track rejections against the design-doc subset and may be able to promote a gap to a supported feature.
583
- - generic [ref=e491]:
584
- - generic [ref=e492]:
585
- - generic [ref=e493]: Was this page helpful?
586
- - generic [ref=e494]:
587
- - button "Yes, this was helpful" [ref=e495] [cursor=pointer]:
588
- - generic [ref=e496]: 👍
589
- - generic [ref=e497]: –
590
- - button "No, this was not helpful" [ref=e498] [cursor=pointer]:
591
- - generic [ref=e499]: 👎
592
- - generic [ref=e500]: –
593
- - link "Edit this page" [ref=e504] [cursor=pointer]:
594
- - /url: https://github.com/gina-io/docs/tree/main/docs/swig/twig/non-goals.mdx
595
- - img [ref=e505]
596
- - text: Edit this page
597
- - navigation "Docs pages" [ref=e509]:
598
- - link "Previous « Parity" [ref=e510] [cursor=pointer]:
599
- - /url: /docs/swig/twig/parity
600
- - generic [ref=e511]: Previous
601
- - generic [ref=e512]: « Parity
602
- - link "Next From upstream Twig »" [ref=e513] [cursor=pointer]:
603
- - /url: /docs/swig/twig/migration
604
- - generic [ref=e514]: Next
605
- - generic [ref=e515]: From upstream Twig »
606
- - list [ref=e519]:
607
- - listitem [ref=e520]:
608
- - link "Unsupported tags" [ref=e521] [cursor=pointer]:
609
- - /url: "#unsupported-tags"
610
- - list [ref=e522]:
611
- - listitem [ref=e523]:
612
- - link "Won't support (security)" [ref=e524] [cursor=pointer]:
613
- - /url: "#wont-support-security"
614
- - listitem [ref=e525]:
615
- - link "Deferred" [ref=e526] [cursor=pointer]:
616
- - /url: "#deferred"
617
- - listitem [ref=e527]:
618
- - link "Macro kwargs" [ref=e528] [cursor=pointer]:
619
- - /url: "#macro-kwargs"
620
- - listitem [ref=e529]:
621
- - 'link "Dynamic {% extends %} and {% from %}" [ref=e530] [cursor=pointer]':
622
- - /url: "#dynamic--extends--and--from-"
623
- - text: Dynamic
624
- - code [ref=e531]: "{% extends %}"
625
- - text: and
626
- - code [ref=e532]: "{% from %}"
627
- - listitem [ref=e533]:
628
- - link "Bracket-notation set" [ref=e534] [cursor=pointer]:
629
- - /url: "#bracket-notation-set"
630
- - text: Bracket-notation
631
- - code [ref=e535]: set
632
- - listitem [ref=e536]:
633
- - link "Conditional branches (deferred)" [ref=e537] [cursor=pointer]:
634
- - /url: "#conditional-branches-deferred"
635
- - listitem [ref=e538]:
636
- - link "Other known gaps" [ref=e539] [cursor=pointer]:
637
- - /url: "#other-known-gaps"
638
- - listitem [ref=e540]:
639
- - link "Reporting a rejection that feels wrong" [ref=e541] [cursor=pointer]:
640
- - /url: "#reporting-a-rejection-that-feels-wrong"
641
- - contentinfo [ref=e542]:
642
- - generic [ref=e543]:
643
- - generic [ref=e544]:
644
- - generic [ref=e545]:
645
- - generic [ref=e546]: Docs
646
- - list [ref=e547]:
647
- - listitem [ref=e548]:
648
- - link "Getting Started" [ref=e549] [cursor=pointer]:
649
- - /url: /docs/intro
650
- - generic [ref=e550]:
651
- - generic [ref=e551]: Community
652
- - list [ref=e552]:
653
- - listitem [ref=e553]:
654
- - link "GitHub Issues(opens in new tab)" [ref=e554] [cursor=pointer]:
655
- - /url: https://github.com/gina-io/gina/issues
656
- - text: GitHub Issues
657
- - img "(opens in new tab)" [ref=e555]
658
- - listitem [ref=e557]:
659
- - link "GitHub Discussions(opens in new tab)" [ref=e558] [cursor=pointer]:
660
- - /url: https://github.com/gina-io/gina/discussions
661
- - text: GitHub Discussions
662
- - img "(opens in new tab)" [ref=e559]
663
- - listitem [ref=e561]:
664
- - link "Support Gina" [ref=e562] [cursor=pointer]:
665
- - /url: /docs/support
666
- - generic [ref=e563]:
667
- - generic [ref=e564]: More
668
- - list [ref=e565]:
669
- - listitem [ref=e566]:
670
- - link "npm(opens in new tab)" [ref=e567] [cursor=pointer]:
671
- - /url: https://www.npmjs.com/package/gina
672
- - text: npm
673
- - img "(opens in new tab)" [ref=e568]
674
- - generic [ref=e571]: Copyright © 2009-2026 gina-io.
675
- - button "Collapse sidebar" [ref=e573] [cursor=pointer]:
676
- - img [ref=e574]