@rhinostone/swig 2.3.0 → 2.4.1
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 +14 -0
- package/ROADMAP.md +10 -0
- package/bin/swig.js +3 -1
- package/dist/swig.js +76 -11
- package/dist/swig.min.js +2 -2
- package/dist/swig.min.js.map +1 -1
- package/lib/lexer.js +6 -0
- package/lib/swig.js +4 -4
- package/lib/tags/import.js +36 -4
- package/package.json +2 -2
package/HISTORY.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
[2.4.1](https://github.com/gina-io/swig/tree/v2.4.1) / 2026-05-22
|
|
2
|
+
-----------------------------------------------------------------
|
|
3
|
+
|
|
4
|
+
* **Fixed** Macros now resolve macros that were imported at the top of their own defining file. Previously such a call compiled to a reference that was never set up in the caller scope, so it rendered empty instead of erroring. Resolves recursively across import depth. gh-2
|
|
5
|
+
|
|
6
|
+
[2.4.0](https://github.com/gina-io/swig/tree/v2.4.0) / 2026-05-15
|
|
7
|
+
-----------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
* **Added** Ternary (`a ? b : c`) and Elvis (`a ?: b`) operator support in template expressions, usable in `{{ }}` output and in tag arguments such as `{% if %}`, `{% set %}`, and `{% for %}`.
|
|
10
|
+
|
|
11
|
+
* **Fixed** `make coverage` now invokes mocha directly via `node node_modules/mocha/bin/_mocha` instead of the `node_modules/.bin/mocha` shim, which exits silently with no output on Node >= 18 — the 95% line-coverage gate is enforced again rather than silently passing.
|
|
12
|
+
|
|
13
|
+
* **Fixed** `swig compile -o <dir>` no longer re-throws `EEXIST` when the output directory already exists. The mkdir guard matched the legacy numeric errno 47, which no longer identifies EEXIST on modern Node; it now matches `e.code === 'EEXIST'`, the stable cross-version identifier.
|
|
14
|
+
|
|
1
15
|
[2.3.0](https://github.com/gina-io/swig/tree/v2.3.0) / 2026-05-14
|
|
2
16
|
-----------------------------------------------------------------
|
|
3
17
|
|
package/ROADMAP.md
CHANGED
|
@@ -22,6 +22,16 @@ _No near-term scheduled items. See [Future (post-2.0)](#future-post-20) for upco
|
|
|
22
22
|
|
|
23
23
|
## Completed
|
|
24
24
|
|
|
25
|
+
### v2.4.1 (May 2026)
|
|
26
|
+
|
|
27
|
+
- Fixed macros rendering empty when they call a macro that was imported at the top of their own defining file. The `import` tag now carries the imported file's own `{% import %}` statements through, so those macros resolve at call time. Resolves recursively across import depth.
|
|
28
|
+
|
|
29
|
+
### v2.4.0 (May 2026)
|
|
30
|
+
|
|
31
|
+
- Native ternary (`a ? b : c`) and Elvis (`a ?: b`) operator support across `@rhinostone/swig` template expressions. Usable in `{{ }}` output and in tag arguments such as `{% if %}`, `{% set %}`, and `{% for %}`. The ternary's `else` branch is required — `{{ x ? "a" }}` throws `Expected colon in ternary expression`. Backend support already existed via `IRConditional` (exercised by `@rhinostone/swig-twig`); this release wires the native parser to produce it.
|
|
32
|
+
- Fixed `swig compile -o <dir>` re-throwing `EEXIST` when the output directory already exists. The mkdir guard matched the legacy numeric `errno 47`, which no longer identifies `EEXIST` on modern Node; the guard now matches `e.code === 'EEXIST'`, the stable cross-version identifier.
|
|
33
|
+
- Fixed `make coverage cov-reporter=travis-cov` silently no-opping the 95% line-coverage gate on Node >= 18. The `coverage:` target invoked the broken `node_modules/.bin/mocha` shim (which exits 0 with no output regardless of pass/fail); it now invokes mocha directly via `node node_modules/mocha/bin/_mocha`, matching the `test:` target.
|
|
34
|
+
|
|
25
35
|
### v2.3.0 (May 2026)
|
|
26
36
|
|
|
27
37
|
- Reduced `@rhinostone/swig`'s production dependency footprint to a single package. `yargs` and `terser` were CLI-only — the library entry point never loaded them — but sat in production `dependencies`, so every library install pulled in their full dependency trees. The CLI's argument parsing is now handled by a small built-in zero-dependency parser; `terser` (used only by `swig compile --minify`) is loaded lazily and ships as a `devDependency`, with `--minify` printing an install hint instead of crashing if it is absent. A library install of `@rhinostone/swig` now pulls in only `@rhinostone/swig-core`. No change to the CLI surface or rendering behavior.
|
package/bin/swig.js
CHANGED
|
@@ -83,7 +83,9 @@ if (argv.o !== 'stdout' && !argv.r) {
|
|
|
83
83
|
try {
|
|
84
84
|
fs.mkdirSync(argv.o);
|
|
85
85
|
} catch (e) {
|
|
86
|
-
|
|
86
|
+
// EEXIST (output dir already exists) is expected. Match on e.code; the
|
|
87
|
+
// legacy numeric errno 47 no longer identifies EEXIST on modern Node.
|
|
88
|
+
if (e.code !== 'EEXIST') {
|
|
87
89
|
throw e;
|
|
88
90
|
}
|
|
89
91
|
}
|
package/dist/swig.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Swig v2.
|
|
1
|
+
/*! Swig v2.4.1 | 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;
|
|
@@ -498,6 +498,8 @@
|
|
|
498
498
|
/** End of a method
|
|
499
499
|
* Currently unused
|
|
500
500
|
METHODEND: 26, */
|
|
501
|
+
/** Ternary question mark (?) */
|
|
502
|
+
QMARK: 27,
|
|
501
503
|
/** Unknown type */
|
|
502
504
|
UNKNOWN: 100
|
|
503
505
|
};
|
|
@@ -2002,6 +2004,12 @@
|
|
|
2002
2004
|
/^\d+(\.\d+)?/
|
|
2003
2005
|
]
|
|
2004
2006
|
},
|
|
2007
|
+
{
|
|
2008
|
+
type: TYPES.QMARK,
|
|
2009
|
+
regex: [
|
|
2010
|
+
/^\?/
|
|
2011
|
+
]
|
|
2012
|
+
},
|
|
2005
2013
|
{
|
|
2006
2014
|
type: TYPES.OPERATOR,
|
|
2007
2015
|
regex: [
|
|
@@ -2265,12 +2273,25 @@
|
|
|
2265
2273
|
self.out.push("{");
|
|
2266
2274
|
self.filterApplyIdx.push(self.out.length - 1);
|
|
2267
2275
|
break;
|
|
2276
|
+
case _t.QMARK:
|
|
2277
|
+
self.out.push(" ? ");
|
|
2278
|
+
self.state.push(token.type);
|
|
2279
|
+
self.filterApplyIdx.pop();
|
|
2280
|
+
break;
|
|
2268
2281
|
case _t.COLON:
|
|
2269
|
-
if (lastState
|
|
2282
|
+
if (lastState === _t.CURLYOPEN) {
|
|
2283
|
+
self.state.push(token.type);
|
|
2284
|
+
self.out.push(":");
|
|
2285
|
+
} else if (lastState === _t.QMARK) {
|
|
2286
|
+
self.state.pop();
|
|
2287
|
+
if (self.out[self.out.length - 1] === " ? ") {
|
|
2288
|
+
self.out[self.out.length - 1] = " || ";
|
|
2289
|
+
} else {
|
|
2290
|
+
self.out.push(" : ");
|
|
2291
|
+
}
|
|
2292
|
+
} else {
|
|
2270
2293
|
utils.throwError("Unexpected colon", self.line, self.filename);
|
|
2271
2294
|
}
|
|
2272
|
-
self.state.push(token.type);
|
|
2273
|
-
self.out.push(":");
|
|
2274
2295
|
self.filterApplyIdx.pop();
|
|
2275
2296
|
break;
|
|
2276
2297
|
case _t.CURLYCLOSE:
|
|
@@ -2610,6 +2631,27 @@
|
|
|
2610
2631
|
var right = parseExpression(info.prec + 1);
|
|
2611
2632
|
left = ir.binaryOp(info.op, left, right);
|
|
2612
2633
|
}
|
|
2634
|
+
if (minPrec === 0) {
|
|
2635
|
+
var qtok = peek();
|
|
2636
|
+
if (qtok && qtok.type === _t.QMARK) {
|
|
2637
|
+
consume();
|
|
2638
|
+
var afterQ = peek();
|
|
2639
|
+
var elseBranch;
|
|
2640
|
+
if (afterQ && afterQ.type === _t.COLON) {
|
|
2641
|
+
consume();
|
|
2642
|
+
elseBranch = parseExpression(0);
|
|
2643
|
+
left = ir.conditional(left, left, elseBranch);
|
|
2644
|
+
} else {
|
|
2645
|
+
var thenBranch = parseExpression(0);
|
|
2646
|
+
var colon = consume();
|
|
2647
|
+
if (!colon || colon.type !== _t.COLON) {
|
|
2648
|
+
bail("Expected colon in ternary expression");
|
|
2649
|
+
}
|
|
2650
|
+
elseBranch = parseExpression(0);
|
|
2651
|
+
left = ir.conditional(left, thenBranch, elseBranch);
|
|
2652
|
+
}
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2613
2655
|
return left;
|
|
2614
2656
|
}
|
|
2615
2657
|
var result = parseExpression(0);
|
|
@@ -2670,7 +2712,7 @@
|
|
|
2670
2712
|
if (typeof self.escape === "string") {
|
|
2671
2713
|
return legacyFallback();
|
|
2672
2714
|
}
|
|
2673
|
-
var depth = 0, hasTopOp = false, hasTopFilter = false, firstTopFilterIdx = -1;
|
|
2715
|
+
var depth = 0, hasTopOp = false, hasTopFilter = false, hasTopTernary = false, firstTopFilterIdx = -1;
|
|
2674
2716
|
for (i = 0; i < tokens.length; i += 1) {
|
|
2675
2717
|
t = tokens[i];
|
|
2676
2718
|
if (depth === 0) {
|
|
@@ -2683,6 +2725,9 @@
|
|
|
2683
2725
|
firstTopFilterIdx = i;
|
|
2684
2726
|
}
|
|
2685
2727
|
}
|
|
2728
|
+
if (t.type === _t.QMARK) {
|
|
2729
|
+
hasTopTernary = true;
|
|
2730
|
+
}
|
|
2686
2731
|
}
|
|
2687
2732
|
if (t.type === _t.PARENOPEN || t.type === _t.FUNCTION || t.type === _t.BRACKETOPEN || t.type === _t.CURLYOPEN || t.type === _t.FILTER) {
|
|
2688
2733
|
depth += 1;
|
|
@@ -2715,7 +2760,7 @@
|
|
|
2715
2760
|
}
|
|
2716
2761
|
}
|
|
2717
2762
|
}
|
|
2718
|
-
if (hasTopOp && hasTopFilter) {
|
|
2763
|
+
if (hasTopTernary || hasTopOp && hasTopFilter) {
|
|
2719
2764
|
var exprPO = self.parseExpr(tokens);
|
|
2720
2765
|
var fcallsPO = escape ? [ir.filterCall("e")] : [];
|
|
2721
2766
|
return ir.output(exprPO, fcallsPO.length > 0 ? fcallsPO : void 0);
|
|
@@ -2814,6 +2859,9 @@
|
|
|
2814
2859
|
}
|
|
2815
2860
|
return ir.output(expr, filterCalls.length > 0 ? filterCalls : void 0);
|
|
2816
2861
|
} catch (e) {
|
|
2862
|
+
if (hasTopTernary) {
|
|
2863
|
+
throw e;
|
|
2864
|
+
}
|
|
2817
2865
|
return legacyFallback();
|
|
2818
2866
|
}
|
|
2819
2867
|
},
|
|
@@ -3014,15 +3062,22 @@
|
|
|
3014
3062
|
options.filename || ""
|
|
3015
3063
|
);
|
|
3016
3064
|
}
|
|
3017
|
-
var ctx = args.pop(),
|
|
3065
|
+
var ctx = args.pop(), macros = [], nestedImports = [];
|
|
3066
|
+
utils.each(args, function(arg) {
|
|
3067
|
+
(arg.isImport ? nestedImports : macros).push(arg);
|
|
3068
|
+
});
|
|
3069
|
+
var allMacros = utils.map(macros, function(arg) {
|
|
3018
3070
|
return arg.name;
|
|
3019
|
-
}).join("|"), out = "_ctx." + ctx + ' = {};\n var _output = "";\n', replacements = utils.map(
|
|
3071
|
+
}).join("|"), out = "_ctx." + ctx + ' = {};\n var _output = "";\n', replacements = utils.map(macros, function(arg) {
|
|
3020
3072
|
return {
|
|
3021
3073
|
ex: new RegExp("_ctx." + arg.name + "(\\W)(?!" + allMacros + ")", "g"),
|
|
3022
3074
|
re: "_ctx." + ctx + "." + arg.name + "$1"
|
|
3023
3075
|
};
|
|
3024
3076
|
});
|
|
3025
|
-
utils.each(
|
|
3077
|
+
utils.each(nestedImports, function(arg) {
|
|
3078
|
+
out += arg.compiled;
|
|
3079
|
+
});
|
|
3080
|
+
utils.each(macros, function(arg) {
|
|
3026
3081
|
var c = arg.compiled;
|
|
3027
3082
|
utils.each(replacements, function(re) {
|
|
3028
3083
|
c = c.replace(re.ex, re.re);
|
|
@@ -3046,7 +3101,17 @@
|
|
|
3046
3101
|
var tokens = swig2.parseFile(importPath, parseOpts).tokens;
|
|
3047
3102
|
utils.each(tokens, function(token2) {
|
|
3048
3103
|
var out = "", macroName;
|
|
3049
|
-
if (!token2 ||
|
|
3104
|
+
if (!token2 || !token2.compile) {
|
|
3105
|
+
return;
|
|
3106
|
+
}
|
|
3107
|
+
if (token2.name === "import") {
|
|
3108
|
+
self.out.push({
|
|
3109
|
+
compiled: token2.compile(compiler, token2.args.slice(), token2.content, [], compileOpts) + "\n",
|
|
3110
|
+
isImport: true
|
|
3111
|
+
});
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3114
|
+
if (token2.name !== "macro") {
|
|
3050
3115
|
return;
|
|
3051
3116
|
}
|
|
3052
3117
|
macroName = token2.args[0];
|
|
@@ -4513,7 +4578,7 @@
|
|
|
4513
4578
|
var loaders = require_loaders2();
|
|
4514
4579
|
var preWalker = require_pre_walker();
|
|
4515
4580
|
var engine = require_engine();
|
|
4516
|
-
exports.version = "2.
|
|
4581
|
+
exports.version = "2.4.1";
|
|
4517
4582
|
var defaultOptions = {
|
|
4518
4583
|
autoescape: true,
|
|
4519
4584
|
varControls: ["{{", "}}"],
|