@rhinostone/swig 2.8.4 → 2.9.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 +9 -0
- package/README.md +7 -5
- package/ROADMAP.md +9 -1
- package/dist/swig.js +77 -2
- package/dist/swig.min.js +19 -2
- package/dist/swig.min.js.map +1 -1
- package/dist/swig.runtime.js +67 -2
- package/dist/swig.runtime.min.js +15 -2
- package/dist/swig.runtime.min.js.map +1 -1
- package/lib/runtime.js +1 -1
- package/lib/swig.js +1 -1
- package/lib/tags/if.js +2 -1
- package/package.json +2 -2
package/HISTORY.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
[2.9.0](https://github.com/gina-io/swig/tree/v2.9.0) / 2026-10-23
|
|
2
|
+
-----------------------------------------------------------------
|
|
3
|
+
|
|
4
|
+
* **Added** An OpenSSF Scorecard badge at the top of the README of each of the five packages. It shows the score that a Scorecard workflow publishes for the repository on every push to develop and every week, and links to the Scorecard report. Documentation only; no runtime or API change.
|
|
5
|
+
|
|
6
|
+
* **Added** swig-twig, swig-jinja2 and swig-django: the `not in` operator, as Twig, Jinja2 and Django have it. `x not in y` negates `x in y` and binds at the same level; where Django cannot run the test it is false, as Django gives it, and where Jinja2 raises swig gives true. Until now `x not in y` failed to parse. Native swig still rejects `not in`.
|
|
7
|
+
|
|
8
|
+
* **Fixed** swig-twig, swig-jinja2 and swig-django: the `in` operator tests membership as Twig, Jinja2 and Django do. In Jinja2 and Django a list holds an equal item, a string a substring and a mapping a key; in Twig a list or a mapping holds a value, compared loosely, and a string holds a string or number as a substring. Until now `in` was the JavaScript operator, which tests keys: `3 in [1, 2, 3]` was false, `0 in [5]` true, and `"b" in "abc"` threw. A test the reference engine cannot run (a number or null container, for instance) is false instead of an error. Native swig keeps the JavaScript `in`.
|
|
9
|
+
|
|
1
10
|
[2.8.4](https://github.com/gina-io/swig/tree/v2.8.4) / 2026-10-21
|
|
2
11
|
-----------------------------------------------------------------
|
|
3
12
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Swig
|
|
2
2
|
====
|
|
3
3
|
|
|
4
|
-
[](https://github.com/gina-io/swig/actions/workflows/ci.yml) [](https://www.npmjs.com/package/@rhinostone/swig) [](https://www.npmjs.com/package/@rhinostone/swig) [](https://socket.dev/npm/package/@rhinostone/swig)
|
|
4
|
+
[](https://github.com/gina-io/swig/actions/workflows/ci.yml) [](https://www.npmjs.com/package/@rhinostone/swig) [](https://www.npmjs.com/package/@rhinostone/swig) [](https://socket.dev/npm/package/@rhinostone/swig) [](https://scorecard.dev/viewer/?uri=github.com/gina-io/swig)
|
|
5
5
|
|
|
6
6
|
> **Multi-flavor template engine** for Node.js and browsers — native Swig syntax (Jinja2/Django-inspired), Twig syntax, Python Jinja2 syntax, and Django Template Language syntax via dedicated frontends sharing one IR backend. [gina-io/swig](https://github.com/gina-io/swig) started as a maintained continuation of the abandoned `paularmstrong/swig` (last released 2014) and is now a standalone project. Security and bug fixes ship here.
|
|
7
7
|
|
|
@@ -32,7 +32,7 @@ Features
|
|
|
32
32
|
--------
|
|
33
33
|
|
|
34
34
|
* Available for node.js **and** major web browsers.
|
|
35
|
-
* **Strict-CSP browser runtime** — AOT-compile templates ahead of time (`swig compile --recursive --register`) and render them with the runtime-only bundle (`dist/swig.runtime.min.js`, ~
|
|
35
|
+
* **Strict-CSP browser runtime** — AOT-compile templates ahead of time (`swig compile --recursive --register`) and render them with the runtime-only bundle (`dist/swig.runtime.min.js`, ~24 KB) — no parser, no `new Function`, no `unsafe-eval` required.
|
|
36
36
|
* [Express](https://expressjs.com/) compatible.
|
|
37
37
|
* **Async template loading** — back the loader with S3 / Redis / HTTP / any callback API: set `loader.async = true` and pass a callback to `renderFile`, or to `compileFile` with `codegenMode: 'async'` in its options, whose compiled function returns a promise of `{ output, exports }`; `extends` / `include` / `import` resolve at render time, dynamic `{% extends %}` paths included.
|
|
38
38
|
* Object-Oriented template inheritance.
|
|
@@ -43,12 +43,14 @@ Features
|
|
|
43
43
|
* Robust without the bloat — a single runtime dependency ([`@rhinostone/swig-core`](https://www.npmjs.com/package/@rhinostone/swig-core)).
|
|
44
44
|
* Extendable and customizable — register custom filters, tags, and loaders per-instance.
|
|
45
45
|
|
|
46
|
-
What's new in v2.
|
|
46
|
+
What's new in v2.9.0
|
|
47
47
|
--------------------
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
> **Behaviour change in `@rhinostone/swig-twig`, `@rhinostone/swig-jinja2` and `@rhinostone/swig-django`.** `x in list` now tests the list's items, not its indices, and `x in "text"` a substring, as Twig, Jinja2 and Django do. In swig-twig, `x in mapping` now tests the mapping's values, as Twig does: write `x in mapping|keys` to test a key. Native `@rhinostone/swig` is unchanged.
|
|
50
50
|
|
|
51
|
-
* **Fixed —
|
|
51
|
+
* **Fixed — `in` tests membership in the Twig, Jinja2 and Django frontends.** In swig-jinja2 and swig-django a list holds an equal item, a string a substring and a mapping a key; in swig-twig a list or a mapping holds a value, compared loosely, and a string holds a string or number as a substring. Until now `in` was the JavaScript operator, which tests keys: `3 in [1, 2, 3]` was false, `0 in [5]` true, and `"b" in "abc"` threw. A test the reference engine cannot run (a number or null container, for instance) is false instead of an error. Native `@rhinostone/swig` keeps the JavaScript `in`, which tests an object's keys or an array's indices.
|
|
52
|
+
* **Added — `not in` in the Twig, Jinja2 and Django frontends.** `x not in y` negates `x in y` and binds at the same level. Where Django cannot run the test it is false, as in Django; where Jinja2 raises, swig gives true. Until now `x not in y` failed to parse. Native `@rhinostone/swig` still rejects `not in`.
|
|
53
|
+
* **Added — an OpenSSF Scorecard badge** at the top of the README of each of the five packages. It shows the score that a Scorecard workflow publishes for the repository on every push to develop and every week, and links to the Scorecard report. Documentation only; no runtime or API change.
|
|
52
54
|
|
|
53
55
|
See the full [HISTORY.md](./HISTORY.md) and [ROADMAP.md](./ROADMAP.md).
|
|
54
56
|
|
package/ROADMAP.md
CHANGED
|
@@ -10,10 +10,11 @@ For bug reports and feature requests, file an issue at [gina-io/swig](https://gi
|
|
|
10
10
|
|
|
11
11
|
| Status | Item |
|
|
12
12
|
| --- | --- |
|
|
13
|
-
| Planned | `in` tests membership in all four flavors: `x in list` tests the elements and `x in "text"` the substring. Today `in` tests an object's keys or an array's indices, so `3 in [1, 2, 3]` is false. |
|
|
14
13
|
| Planned | swig-jinja2 parity: loop filters (`{% for x in xs if cond %}`), `not(x)` written without a space, the `none` / `True` / `False` constants, for-loops over a mapping and over a list of pairs as Jinja2 iterates them, and the builtin filters `attr`, `filesizeformat`, `forceescape`, `items`, `string` and `urlize`. |
|
|
15
14
|
| Planned | swig-django: two-variable for-loops over a list of pairs unpack each pair, as Django does. |
|
|
16
15
|
| Planned | Unknown filter and test names rejected when a Twig, Jinja2 or Django template compiles; today the error comes only at render, or never when the branch does not run. Native swig already rejects an unknown filter. |
|
|
16
|
+
| Planned | swig-django: `not` binds below the comparisons and `in`, as in Django; today `not x == y` reads as `(not x) == y`. |
|
|
17
|
+
| Planned | swig-jinja2 and swig-django: `==` and `!=` compare as Python does; today they are JavaScript's loose operators, so `1 == "1"` is true and `[1] == [1]` false. |
|
|
17
18
|
|
|
18
19
|
## Future (post-2.0)
|
|
19
20
|
|
|
@@ -27,6 +28,13 @@ For bug reports and feature requests, file an issue at [gina-io/swig](https://gi
|
|
|
27
28
|
|
|
28
29
|
## Completed
|
|
29
30
|
|
|
31
|
+
### v2.9.0 (October 2026)
|
|
32
|
+
|
|
33
|
+
- `in` tests membership in swig-twig, swig-jinja2 and swig-django, as Twig, Jinja2 and Django do: a list holds an item, a string a substring, and a mapping a key (Jinja2, Django) or a value (Twig). Native swig keeps the JavaScript `in`, which tests an object's keys or an array's indices.
|
|
34
|
+
- `not in` in swig-twig, swig-jinja2 and swig-django.
|
|
35
|
+
- An OpenSSF Scorecard badge on the README of each package.
|
|
36
|
+
- All five packages released in lockstep at `2.9.0`.
|
|
37
|
+
|
|
30
38
|
### v2.8.4 (October 2026)
|
|
31
39
|
|
|
32
40
|
- Fixed the `autoescape` option of `@rhinostone/swig` set to an escape strategy name: `'js'` and `'html'` work as documented. The value was written into the compiled template as code, so these failed with a ReferenceError and an option built from untrusted input could run as code; a value holding a double quote or a line break is now rejected with an error.
|
package/dist/swig.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Swig v2.
|
|
1
|
+
/*! Swig v2.9.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/tn0ms/DateZ/blob/master/LISENCE */
|
|
3
3
|
(() => {
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -250,6 +250,58 @@
|
|
|
250
250
|
}
|
|
251
251
|
return cur;
|
|
252
252
|
};
|
|
253
|
+
function sameItem(a, b, twig) {
|
|
254
|
+
if (a === b) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (!twig) {
|
|
258
|
+
return (a === null || a === void 0) && (b === null || b === void 0);
|
|
259
|
+
}
|
|
260
|
+
if (a !== null && typeof a === "object" || b !== null && typeof b === "object" || typeof a === "function" || typeof b === "function") {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
return a == b;
|
|
264
|
+
}
|
|
265
|
+
function membership(needle, haystack, twig) {
|
|
266
|
+
var keys, i;
|
|
267
|
+
if (typeof haystack === "string") {
|
|
268
|
+
if (typeof needle === "string" || twig && typeof needle === "number") {
|
|
269
|
+
return haystack.indexOf(String(needle)) !== -1;
|
|
270
|
+
}
|
|
271
|
+
return twig ? false : null;
|
|
272
|
+
}
|
|
273
|
+
if (haystack === null || typeof haystack !== "object") {
|
|
274
|
+
return twig ? false : null;
|
|
275
|
+
}
|
|
276
|
+
if (exports.isArray(haystack)) {
|
|
277
|
+
for (i = 0; i < haystack.length; i += 1) {
|
|
278
|
+
if (sameItem(needle, haystack[i], twig)) {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
if (twig) {
|
|
285
|
+
keys = Object.keys(haystack);
|
|
286
|
+
for (i = 0; i < keys.length; i += 1) {
|
|
287
|
+
if (sameItem(needle, haystack[keys[i]], true)) {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (typeof needle === "string" || typeof needle === "number") {
|
|
294
|
+
return Object.prototype.hasOwnProperty.call(haystack, String(needle));
|
|
295
|
+
}
|
|
296
|
+
return needle !== null && typeof needle === "object" ? null : false;
|
|
297
|
+
}
|
|
298
|
+
exports.isIn = function(needle, haystack, mode, negate) {
|
|
299
|
+
var found = membership(needle, haystack, mode === "twig");
|
|
300
|
+
if (found === null) {
|
|
301
|
+
return negate === true && mode !== "django";
|
|
302
|
+
}
|
|
303
|
+
return negate === true ? !found : found;
|
|
304
|
+
};
|
|
253
305
|
}
|
|
254
306
|
});
|
|
255
307
|
|
|
@@ -1923,12 +1975,18 @@
|
|
|
1923
1975
|
}
|
|
1924
1976
|
return js;
|
|
1925
1977
|
}
|
|
1978
|
+
function membershipMode(flag) {
|
|
1979
|
+
return flag === "twig" || flag === "django" ? flag : "jinja2";
|
|
1980
|
+
}
|
|
1926
1981
|
function emitBinaryOp(node, d) {
|
|
1927
1982
|
var left = groupOperand(node.left, emitExpr(node.left, d)), right = groupOperand(node.right, emitExpr(node.right, d));
|
|
1928
1983
|
if (isArithmeticOp(node.op)) {
|
|
1929
1984
|
return left + " " + node.op + " " + right;
|
|
1930
1985
|
}
|
|
1931
1986
|
if (node.op === "in") {
|
|
1987
|
+
if (node.membership) {
|
|
1988
|
+
return "_utils.isIn(" + left + ", " + right + ', "' + membershipMode(node.membership) + '"' + (node.negate ? ", true" : "") + ")";
|
|
1989
|
+
}
|
|
1932
1990
|
return left + " in " + right;
|
|
1933
1991
|
}
|
|
1934
1992
|
if (node.op === "~") {
|
|
@@ -4836,7 +4894,7 @@
|
|
|
4836
4894
|
var loaders = require_loaders2();
|
|
4837
4895
|
var preWalker = require_pre_walker();
|
|
4838
4896
|
var engine = require_engine();
|
|
4839
|
-
exports.version = "2.
|
|
4897
|
+
exports.version = "2.9.0";
|
|
4840
4898
|
var defaultOptions = {
|
|
4841
4899
|
autoescape: true,
|
|
4842
4900
|
varControls: ["{{", "}}"],
|
|
@@ -5076,6 +5134,19 @@
|
|
|
5076
5134
|
* `_dangerousProps` segments are rejected at runtime as defense-in-depth.
|
|
5077
5135
|
* @private
|
|
5078
5136
|
*/
|
|
5137
|
+
/*!
|
|
5138
|
+
* Item equality for `isIn`. Jinja2 and Django compare with `===` and treat
|
|
5139
|
+
* null and a missing value as equal (Django reads both as None). Twig
|
|
5140
|
+
* compares two scalars with `==`, as swig-twig's `==` does, and anything
|
|
5141
|
+
* else by identity. @private
|
|
5142
|
+
*/
|
|
5143
|
+
/*!
|
|
5144
|
+
* The membership test of `isIn`: true or false, or null when Jinja2 and
|
|
5145
|
+
* Django cannot run it, where Python raises a TypeError: a container that is
|
|
5146
|
+
* not a list, a string or a mapping, a needle that is not a string tested
|
|
5147
|
+
* against a string, or a list or an object tested against a mapping's keys.
|
|
5148
|
+
* Twig runs every test. @private
|
|
5149
|
+
*/
|
|
5079
5150
|
/*!
|
|
5080
5151
|
* Attach `loc` to the node if provided, skipping the assignment otherwise
|
|
5081
5152
|
* so consumers can tell "no source location available" from
|
|
@@ -5177,6 +5248,10 @@
|
|
|
5177
5248
|
* wraps a unary operand, for the positions where its prefix would bind
|
|
5178
5249
|
* differently (`-(-x)`, `(-x)[0]`). @private
|
|
5179
5250
|
*/
|
|
5251
|
+
/*!
|
|
5252
|
+
* The flavor name `emitBinaryOp` writes into an `_utils.isIn` call: one of
|
|
5253
|
+
* the three names `isIn` knows, never the flag's own text. @private
|
|
5254
|
+
*/
|
|
5180
5255
|
/*!
|
|
5181
5256
|
* Emit a function / method invocation. Three callee shapes:
|
|
5182
5257
|
* 1. Single-segment VarRef (`foo(...)`) — FUNCTION-token pattern with
|