@rhinostone/swig 2.4.3 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/HISTORY.md CHANGED
@@ -1,3 +1,8 @@
1
+ [2.5.0](https://github.com/gina-io/swig/tree/v2.5.0) / 2026-05-27
2
+ -----------------------------------------------------------------
3
+
4
+ * **Added** @rhinostone/swig-jinja2 — a Python Jinja2-syntax frontend on the shared swig-core engine (13 tags, 39 filters, 16 is-tests, async loader support, autoescape and CVE-2023-25345 guards inherited from swig-core). Releases in lockstep with @rhinostone/swig, @rhinostone/swig-core, and @rhinostone/swig-twig.
5
+
1
6
  [2.4.3](https://github.com/gina-io/swig/tree/v2.4.3) / 2026-05-22
2
7
  -----------------------------------------------------------------
3
8
 
@@ -57,7 +62,7 @@
57
62
  [2.0.0](https://github.com/gina-io/swig/tree/v2.0.0) / 2026-05-06
58
63
  -----------------------------------------------------------------
59
64
 
60
- * **Fixed** Refreshed stale external URL references across documentation surfaces from the 2026-04-13 link-health audit (gina-io/gina#18). `README.md`, `packages/swig-core/README.md`, `packages/swig-twig/README.md` — swapped `www.npmjs.org` for the canonical `www.npmjs.com` in the NPM badges (the old `.org` form now redirects). `README.md` — rewrote the "Swig v0.x → v1.x migration notes" bullet, since the `paularmstrong/swig/wiki/Migrating-from-...` page was deleted upstream (302s to repo root). `HISTORY.md` — dropped the three dead upstream-wiki links around the v1.0.0 / v1.0.0-pre1 entries; the breaking-change prose stays intact. `browser/comments.js` — updated the DateZ `@license` URL from `TomoUniversalis/DateZ` to `ocrybit/DateZ` (the upstream GitHub user was renamed). `packages/swig-core/lib/dateformatter.js` — dropped the dead `http://tomouniversalis.com` parenthetical from the DateZ copyright comment (domain NXDOMAINs). `.changes/v1.0.0.md` and `.changes/v1.0.0-pre1.md` synced to match the rewritten `HISTORY.md` lines so future `changie merge` runs stay idempotent. Documentation and comment-only; no runtime or API change.
65
+ * **Fixed** Refreshed stale external URL references across documentation surfaces from the 2026-04-13 link-health audit (gina-io/gina#18). `README.md`, `packages/swig-core/README.md`, `packages/swig-twig/README.md` — swapped `www.npmjs.org` for the canonical `www.npmjs.com` in the NPM badges (the old `.org` form now redirects). `README.md` — rewrote the "Swig v0.x → v1.x migration notes" bullet, since the `paularmstrong/swig/wiki/Migrating-from-...` page was deleted upstream (302s to repo root). `HISTORY.md` — dropped the three dead upstream-wiki links around the v1.0.0 / v1.0.0-pre1 entries; the breaking-change prose stays intact. `browser/comments.js` — updated the DateZ `@license` URL from `TomoUniversalis/DateZ` to `ocrybit/DateZ` (the upstream GitHub user was renamed). `packages/swig-core/lib/dateformatter.js` — dropped the dead `tomouniversalis.com` parenthetical from the DateZ copyright comment (domain NXDOMAINs). `.changes/v1.0.0.md` and `.changes/v1.0.0-pre1.md` synced to match the rewritten `HISTORY.md` lines so future `changie merge` runs stay idempotent. Documentation and comment-only; no runtime or API change.
61
66
 
62
67
  * **Changed** `2.0.0` stable. Multi-flavor architecture (introduced across `2.0.0-alpha.1` through `2.0.0-alpha.5`) is the production-ready cut. No functional or API changes since `2.0.0-alpha.5`; the `alpha.6`–`alpha.8` cycle was metadata republishes plus removal of the soft-deprecated `exports.parse` Path B wrapper from `@rhinostone/swig-twig`. IR ABI is stable from this release onward; cross-package dependencies pin exact versions and frontends + core release in lockstep. README messaging refreshed across all three packages to reflect production-ready status; package descriptions cleaned of historical internal-tracking references.
63
68
 
package/README.md CHANGED
@@ -64,7 +64,7 @@ For Twig syntax:
64
64
  Documentation
65
65
  -------------
66
66
 
67
- User-facing documentation lives in the Gina Docusaurus site under the [Swig Template Engine](https://gina.io/docs/swig) section, maintained in [gina-io/docs](https://github.com/gina-io/docs) at `docs/swig/`. The JSDoc blocks in `lib/swig.js`, `lib/filters.js`, `lib/tags/`, and `lib/loaders/` remain the canonical source-of-truth for the public API and are mirrored into the Docusaurus pages.
67
+ User-facing documentation lives in the Gina Docusaurus site under the [Swig Template Engine](https://gina.io/docs/swig) section, maintained in [gina-io/docs](https://github.com/gina-io/docs) at `docs/templating/swig/`. The JSDoc blocks in `lib/swig.js`, `lib/filters.js`, `lib/tags/`, and `lib/loaders/` remain the canonical source-of-truth for the public API and are mirrored into the Docusaurus pages.
68
68
 
69
69
  Basic Example
70
70
  -------------
package/ROADMAP.md CHANGED
@@ -15,13 +15,18 @@ _No near-term scheduled items. See [Future (post-2.0)](#future-post-20) for upco
15
15
  | Status | Item |
16
16
  | --- | --- |
17
17
  | Planned | Async parse path for dynamic targets — full support for `{% extends parent_var %}`, `{% include user_template %}`, and runtime-resolved `import` / `from` paths on the async-codegen branch. Static-target async dispatch shipped in 2.2.0; dynamic-target support is on hold pending consumer demand. |
18
- | Planned | Ship Jinja2 and Django frontends as additional `@rhinostone/swig-*` packages. On demand — when there's concrete user demand. |
18
+ | Planned | Ship a Django frontend as an additional `@rhinostone/swig-*` package. On demand — when there's concrete user demand. (The Jinja2 frontend shipped in `2.5.0`.) |
19
19
  | Planned | Test framework migration. Replace mocha 1.x + expect.js with `node:test` + `node:assert/strict`, swap mocha-phantomjs for a modern browser-test harness, swap blanket for `c8`. (The Node engines bump is upstream-driven by gina and is being treated as done.) |
20
20
 
21
21
  ---
22
22
 
23
23
  ## Completed
24
24
 
25
+ ### v2.5.0 (May 2026)
26
+
27
+ - Added `@rhinostone/swig-jinja2`, a Python Jinja2-syntax frontend on the shared `@rhinostone/swig-core` engine — the third dialect in the multi-flavor family alongside native swig and `@rhinostone/swig-twig`. Ships 13 tags (`set`, `if` / `elif` / `else`, `for` with `else`, `block`, `extends`, `include`, `macro`, `import`, `from`, `raw`, `filter`, `with`, `autoescape`), 39 filters, and 16 `is` tests, plus the `**` / `//` / `~` operators, inline-if, Python slicing, and `{{- … -}}` whitespace control. Async loader support via `renderFileAsync` / `compileFileAsync`. Autoescape and the CVE-2023-25345 guards are inherited from `@rhinostone/swig-core`. Every filter and is-test was cross-checked against Python Jinja2 3.x; the behavioural differences (where the JavaScript runtime diverges from CPython) and the explicit non-goals (no sandboxed rendering, `{% call %}` / `{% do %}` / `{% trans %}`, the `map` / `select` filter family, macro kwargs) are documented in the Jinja2 templating guide.
28
+ - All four packages (`@rhinostone/swig`, `@rhinostone/swig-core`, `@rhinostone/swig-twig`, `@rhinostone/swig-jinja2`) released in lockstep at `2.5.0`. `@rhinostone/swig-core` gained additive `slice` and `coerceOutput` runtime helpers consumed by the Jinja2 frontend; native swig and Twig are functionally unchanged.
29
+
25
30
  ### v2.4.3 (May 2026)
26
31
 
27
32
  - Fixed native `import` leaking an imported file's own import aliases into the importing template's scope. The `{% import %}` carry-through added in `2.4.1` emitted those nested imports bare into the caller's context, where the leaked alias could clobber a same-named caller variable, corrupt a macro when the caller later reassigned that name (macros read the live context at call time), or cascade across import depth. The nested imports are now re-homed under the importing alias and kept local to the file that declares them — matching `@rhinostone/swig-twig`'s scoping and the Jinja2/Twig import contract. Macros still resolve their own file's imports at call time.
package/dist/swig.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! Swig v2.4.3 | https://github.com/gina-io/swig | @license https://github.com/gina-io/swig/blob/master/LICENSE */
1
+ /*! Swig v2.5.0 | https://github.com/gina-io/swig | @license https://github.com/gina-io/swig/blob/master/LICENSE */
2
2
  /*! DateZ (c) 2011 Tomo Universalis | @license https://github.com/ocrybit/DateZ/blob/master/LISENCE */
3
3
  (() => {
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -139,6 +139,68 @@
139
139
  }
140
140
  return out;
141
141
  };
142
+ exports.slice = function(obj, start, stop, step) {
143
+ var isString = typeof obj === "string", length, lower, upper, s, e, result = [], i;
144
+ function toInt(n) {
145
+ n = Number(n);
146
+ if (isNaN(n)) {
147
+ return NaN;
148
+ }
149
+ return n < 0 ? Math.ceil(n) : Math.floor(n);
150
+ }
151
+ function clamp(v, dflt) {
152
+ if (v === null || v === void 0) {
153
+ return dflt;
154
+ }
155
+ v = toInt(v);
156
+ if (isNaN(v)) {
157
+ return dflt;
158
+ }
159
+ if (v < 0) {
160
+ v += length;
161
+ if (v < lower) {
162
+ v = lower;
163
+ }
164
+ } else if (v > upper) {
165
+ v = upper;
166
+ }
167
+ return v;
168
+ }
169
+ if (obj === null || obj === void 0 || typeof obj.length !== "number") {
170
+ return isString ? "" : [];
171
+ }
172
+ length = obj.length;
173
+ if (step === null || step === void 0) {
174
+ step = 1;
175
+ } else {
176
+ step = toInt(step);
177
+ if (isNaN(step) || step === 0) {
178
+ step = 1;
179
+ }
180
+ }
181
+ if (step < 0) {
182
+ lower = -1;
183
+ upper = length - 1;
184
+ } else {
185
+ lower = 0;
186
+ upper = length;
187
+ }
188
+ s = clamp(start, step < 0 ? upper : lower);
189
+ e = clamp(stop, step < 0 ? lower : upper);
190
+ if (step > 0) {
191
+ for (i = s; i < e; i += step) {
192
+ result.push(obj[i]);
193
+ }
194
+ } else {
195
+ for (i = s; i > e; i += step) {
196
+ result.push(obj[i]);
197
+ }
198
+ }
199
+ return isString ? result.join("") : result;
200
+ };
201
+ exports.coerceOutput = function(v) {
202
+ return v === null || v === void 0 ? "" : v;
203
+ };
142
204
  }
143
205
  });
144
206
 
@@ -1366,7 +1428,22 @@
1366
1428
  return;
1367
1429
  }
1368
1430
  });
1369
- out += "(function () {\n var __l = " + forIterable + ', __len = (_utils.isArray(__l) || typeof __l === "string") ? __l.length : _utils.keys(__l).length;\n if (!__l) { return; }\n var ' + ctxloopcache + " = { loop: " + ctxloop + ", " + forVal + ": " + ctx + forVal + ", " + forKey + ": " + ctx + forKey + " };\n " + ctxloop + " = { first: false, index: 1, index0: 0, revindex: __len, revindex0: __len - 1, length: __len, last: false };\n _utils.each(__l, function (" + forVal + ", " + forKey + ") {\n " + ctx + forVal + " = " + forVal + ";\n " + ctx + forKey + " = " + forKey + ";\n " + ctxloop + ".key = " + forKey + ";\n " + ctxloop + ".first = (" + ctxloop + ".index0 === 0);\n " + ctxloop + ".last = (" + ctxloop + ".revindex0 === 0);\n " + forBodyJS + " " + ctxloop + ".index += 1; " + ctxloop + ".index0 += 1; " + ctxloop + ".revindex -= 1; " + ctxloop + ".revindex0 -= 1;\n });\n " + ctxloop + " = " + ctxloopcache + ".loop;\n " + ctx + forVal + " = " + ctxloopcache + "." + forVal + ";\n " + ctx + forKey + " = " + ctxloopcache + "." + forKey + ";\n " + ctxloopcache + " = undefined;\n})();\n";
1431
+ var forEmptyCheck = " if (!__l) { return; }\n";
1432
+ if (node.emptyBody) {
1433
+ var forEmptyJS = "";
1434
+ utils.each(node.emptyBody, function(b) {
1435
+ if (b.type === "LegacyJS") {
1436
+ forEmptyJS += b.js;
1437
+ return;
1438
+ }
1439
+ if (b.type === "Text" || b.type === "Raw") {
1440
+ forEmptyJS += '_output += "' + escapeTextValue(b.value) + '";\n';
1441
+ return;
1442
+ }
1443
+ });
1444
+ forEmptyCheck = " if (!__l || !__len) {\n" + forEmptyJS + " return;\n }\n";
1445
+ }
1446
+ out += "(function () {\n var __l = " + forIterable + ', __len = (_utils.isArray(__l) || typeof __l === "string") ? __l.length : _utils.keys(__l).length;\n' + forEmptyCheck + " var " + ctxloopcache + " = { loop: " + ctxloop + ", " + forVal + ": " + ctx + forVal + ", " + forKey + ": " + ctx + forKey + " };\n " + ctxloop + " = { first: false, index: 1, index0: 0, revindex: __len, revindex0: __len - 1, length: __len, last: false };\n _utils.each(__l, function (" + forVal + ", " + forKey + ") {\n " + ctx + forVal + " = " + forVal + ";\n " + ctx + forKey + " = " + forKey + ";\n " + ctxloop + ".key = " + forKey + ";\n " + ctxloop + ".first = (" + ctxloop + ".index0 === 0);\n " + ctxloop + ".last = (" + ctxloop + ".revindex0 === 0);\n " + forBodyJS + " " + ctxloop + ".index += 1; " + ctxloop + ".index0 += 1; " + ctxloop + ".revindex -= 1; " + ctxloop + ".revindex0 -= 1;\n });\n " + ctxloop + " = " + ctxloopcache + ".loop;\n " + ctx + forVal + " = " + ctxloopcache + "." + forVal + ";\n " + ctx + forKey + " = " + ctxloopcache + "." + forKey + ";\n " + ctxloopcache + " = undefined;\n})();\n";
1370
1447
  return;
1371
1448
  }
1372
1449
  if (node.type === "Macro") {
@@ -1381,11 +1458,14 @@
1381
1458
  return;
1382
1459
  }
1383
1460
  });
1384
- var macroParams = node.params || [], macroSigJS, macroIndexOfJS;
1461
+ var macroParams = node.params || [], macroSigJS, macroIndexOfJS, macroDefaultsJS = "";
1385
1462
  if (macroParams.length && typeof macroParams[0] === "object" && macroParams[0] !== null && typeof macroParams[0].name === "string") {
1386
1463
  var macroNames = [];
1387
1464
  utils.each(macroParams, function(p) {
1388
1465
  macroNames.push(p.name);
1466
+ if (p["default"]) {
1467
+ macroDefaultsJS += " if (" + p.name + " === undefined) { " + p.name + " = " + exports.emitExpr(p["default"]) + "; }\n";
1468
+ }
1389
1469
  });
1390
1470
  macroSigJS = macroNames.join(", ");
1391
1471
  var macroJsonNames = [];
@@ -1397,7 +1477,7 @@
1397
1477
  macroSigJS = macroParams.join("");
1398
1478
  macroIndexOfJS = '"' + macroParams.join('","') + '"';
1399
1479
  }
1400
- out += "_ctx." + node.name + " = function (" + macroSigJS + ') {\n var _output = "",\n __ctx = _utils.extend({}, _ctx);\n _utils.each(_ctx, function (v, k) {\n if ([' + macroIndexOfJS + "].indexOf(k) !== -1) { delete _ctx[k]; }\n });\n" + macroBodyJS + "\n _ctx = _utils.extend(_ctx, __ctx);\n return _output;\n};\n_ctx." + node.name + ".safe = true;\n";
1480
+ out += "_ctx." + node.name + " = function (" + macroSigJS + ') {\n var _output = "",\n __ctx = _utils.extend({}, _ctx);\n' + macroDefaultsJS + " _utils.each(_ctx, function (v, k) {\n if ([" + macroIndexOfJS + "].indexOf(k) !== -1) { delete _ctx[k]; }\n });\n" + macroBodyJS + "\n _ctx = _utils.extend(_ctx, __ctx);\n return _output;\n};\n_ctx." + node.name + ".safe = true;\n";
1401
1481
  if (options && options.codegenMode === "async") {
1402
1482
  if (_security.dangerousProps.indexOf(node.name) !== -1) {
1403
1483
  throw new Error('Macro name "' + node.name + '" is reserved.');
@@ -1625,7 +1705,11 @@
1625
1705
  outExprJS = '_filters["' + fc.name + '"](' + outExprJS + fcArgsJS + ")";
1626
1706
  });
1627
1707
  }
1628
- out += "_output += " + outExprJS + ";\n";
1708
+ if (node.coerce) {
1709
+ out += "_output += _utils.coerceOutput(" + outExprJS + ");\n";
1710
+ } else {
1711
+ out += "_output += " + outExprJS + ";\n";
1712
+ }
1629
1713
  return;
1630
1714
  }
1631
1715
  if (node.type === "Filter") {
@@ -2356,9 +2440,9 @@
2356
2440
  * `parseExpr` emits structured IR that {@link backend.emitExpr}
2357
2441
  * later lowers into an equivalent JS-source fragment. `.parse()` is
2358
2442
  * unchanged and remains the production path; `parseExpr` is the
2359
- * incoming target shape for Phase 2 (#T15), introduced additively in
2360
- * Session 14b so the IR grammar can be proven against real lexer
2361
- * output before consumers are flipped in Commits 3-8.
2443
+ * incoming target shape for the IR migration, introduced additively
2444
+ * so the IR grammar can be proven against real lexer output before
2445
+ * consumers are flipped over to it.
2362
2446
  *
2363
2447
  * The CVE-2023-25345 prototype-chain guards (`_dangerousProps` on
2364
2448
  * VAR segments, DOTKEY matches, STRING-inside-BRACKETOPEN values,
@@ -4598,7 +4682,7 @@
4598
4682
  var loaders = require_loaders2();
4599
4683
  var preWalker = require_pre_walker();
4600
4684
  var engine = require_engine();
4601
- exports.version = "2.4.3";
4685
+ exports.version = "2.5.0";
4602
4686
  var defaultOptions = {
4603
4687
  autoescape: true,
4604
4688
  varControls: ["{{", "}}"],