@rhinostone/swig-core 2.3.0 → 2.4.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/lib/tokenparser.js +78 -4
- package/lib/tokentypes.js +2 -0
- package/package.json +1 -1
package/lib/tokenparser.js
CHANGED
|
@@ -310,12 +310,39 @@ TokenParser.prototype = {
|
|
|
310
310
|
self.filterApplyIdx.push(self.out.length - 1);
|
|
311
311
|
break;
|
|
312
312
|
|
|
313
|
+
case _t.QMARK:
|
|
314
|
+
// Ternary `?` — emit a flat operator and mark the state stack so
|
|
315
|
+
// the matching `:` is recognised as the alternative-branch
|
|
316
|
+
// separator (not an object-literal colon). The lowerExpr ->
|
|
317
|
+
// parseExpr IR path does the real ternary codegen for built-in
|
|
318
|
+
// tags; emitting `?`/`:` flat here just keeps the legacy tag-arg
|
|
319
|
+
// walk from aborting so `args` is built for every tag.
|
|
320
|
+
self.out.push(' ? ');
|
|
321
|
+
self.state.push(token.type);
|
|
322
|
+
self.filterApplyIdx.pop();
|
|
323
|
+
break;
|
|
324
|
+
|
|
313
325
|
case _t.COLON:
|
|
314
|
-
if (lastState
|
|
326
|
+
if (lastState === _t.CURLYOPEN) {
|
|
327
|
+
self.state.push(token.type);
|
|
328
|
+
self.out.push(':');
|
|
329
|
+
} else if (lastState === _t.QMARK) {
|
|
330
|
+
// Ternary alternative branch. Pop the QMARK marker the QMARK
|
|
331
|
+
// case pushed. A full ternary (`a ? b : c`) has a non-empty
|
|
332
|
+
// then-branch, so the colon emits as a flat operator. The Elvis
|
|
333
|
+
// shorthand (`a ?: b`) has an empty then-branch — the `?` we
|
|
334
|
+
// emitted is still the last fragment — so rewrite it to `||`,
|
|
335
|
+
// the single-evaluation equivalent of `a ? a : b`. A bare
|
|
336
|
+
// `a ? : b` would be invalid JS.
|
|
337
|
+
self.state.pop();
|
|
338
|
+
if (self.out[self.out.length - 1] === ' ? ') {
|
|
339
|
+
self.out[self.out.length - 1] = ' || ';
|
|
340
|
+
} else {
|
|
341
|
+
self.out.push(' : ');
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
315
344
|
utils.throwError('Unexpected colon', self.line, self.filename);
|
|
316
345
|
}
|
|
317
|
-
self.state.push(token.type);
|
|
318
|
-
self.out.push(':');
|
|
319
346
|
self.filterApplyIdx.pop();
|
|
320
347
|
break;
|
|
321
348
|
|
|
@@ -647,6 +674,37 @@ TokenParser.prototype = {
|
|
|
647
674
|
var right = parseExpression(info.prec + 1);
|
|
648
675
|
left = ir.binaryOp(info.op, left, right);
|
|
649
676
|
}
|
|
677
|
+
// Ternary + Elvis — binds looser than every binary op, so it is only
|
|
678
|
+
// handled at the top-level minPrec === 0 entry. Recursive calls for a
|
|
679
|
+
// binary op's RHS run at prec + 1 >= 1 and skip this branch, which is
|
|
680
|
+
// what lets `a + b ? c : d` parse as `(a + b) ? c : d`. Recursive
|
|
681
|
+
// parseExpression(0) calls (arg-list elements, object-literal values,
|
|
682
|
+
// grouped sub-expressions) still get ternary via their own entry.
|
|
683
|
+
//
|
|
684
|
+
// Elvis shorthand `a ?: b` lowers to Conditional(a, a, b). The `a`
|
|
685
|
+
// subexpression is evaluated twice by downstream emitters — a
|
|
686
|
+
// documented consequence of the transliteration.
|
|
687
|
+
if (minPrec === 0) {
|
|
688
|
+
var qtok = peek();
|
|
689
|
+
if (qtok && qtok.type === _t.QMARK) {
|
|
690
|
+
consume();
|
|
691
|
+
var afterQ = peek();
|
|
692
|
+
var elseBranch;
|
|
693
|
+
if (afterQ && afterQ.type === _t.COLON) {
|
|
694
|
+
consume();
|
|
695
|
+
elseBranch = parseExpression(0);
|
|
696
|
+
left = ir.conditional(left, left, elseBranch);
|
|
697
|
+
} else {
|
|
698
|
+
var thenBranch = parseExpression(0);
|
|
699
|
+
var colon = consume();
|
|
700
|
+
if (!colon || colon.type !== _t.COLON) {
|
|
701
|
+
bail('Expected colon in ternary expression');
|
|
702
|
+
}
|
|
703
|
+
elseBranch = parseExpression(0);
|
|
704
|
+
left = ir.conditional(left, thenBranch, elseBranch);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
650
708
|
return left;
|
|
651
709
|
}
|
|
652
710
|
|
|
@@ -706,6 +764,7 @@ TokenParser.prototype = {
|
|
|
706
764
|
var depth = 0,
|
|
707
765
|
hasTopOp = false,
|
|
708
766
|
hasTopFilter = false,
|
|
767
|
+
hasTopTernary = false,
|
|
709
768
|
firstTopFilterIdx = -1;
|
|
710
769
|
for (i = 0; i < tokens.length; i += 1) {
|
|
711
770
|
t = tokens[i];
|
|
@@ -718,6 +777,9 @@ TokenParser.prototype = {
|
|
|
718
777
|
hasTopFilter = true;
|
|
719
778
|
if (firstTopFilterIdx < 0) { firstTopFilterIdx = i; }
|
|
720
779
|
}
|
|
780
|
+
if (t.type === _t.QMARK) {
|
|
781
|
+
hasTopTernary = true;
|
|
782
|
+
}
|
|
721
783
|
}
|
|
722
784
|
if (t.type === _t.PARENOPEN || t.type === _t.FUNCTION ||
|
|
723
785
|
t.type === _t.BRACKETOPEN || t.type === _t.CURLYOPEN ||
|
|
@@ -779,7 +841,13 @@ TokenParser.prototype = {
|
|
|
779
841
|
// trailing FILTER / FILTEREMPTY and wraps the atom in an
|
|
780
842
|
// IRFilterCallExpr at the right tree depth. Autoescape remains
|
|
781
843
|
// a top-level wrap (single `e` filterCall appended).
|
|
782
|
-
|
|
844
|
+
//
|
|
845
|
+
// A top-level ternary (`{{ a ? b : c }}`) takes the same route:
|
|
846
|
+
// the prefix/filter-drain path below would slice the stream at
|
|
847
|
+
// the first top-level filter (e.g. a filter inside a branch,
|
|
848
|
+
// `{{ x ? a|upper : b }}`) and mis-parse it. Full-stream
|
|
849
|
+
// parseExpr consumes the whole ternary, branch filters included.
|
|
850
|
+
if (hasTopTernary || (hasTopOp && hasTopFilter)) {
|
|
783
851
|
var exprPO = self.parseExpr(tokens);
|
|
784
852
|
var fcallsPO = escape ? [ir.filterCall('e')] : [];
|
|
785
853
|
return ir.output(exprPO, fcallsPO.length > 0 ? fcallsPO : undefined);
|
|
@@ -874,6 +942,12 @@ TokenParser.prototype = {
|
|
|
874
942
|
|
|
875
943
|
return ir.output(expr, filterCalls.length > 0 ? filterCalls : undefined);
|
|
876
944
|
} catch (e) {
|
|
945
|
+
// A top-level ternary cannot be expressed by the legacy parseToken
|
|
946
|
+
// path (no QMARK handling), so legacyFallback would only throw a
|
|
947
|
+
// worse error ("Unexpected colon") or emit garbage. Re-throw
|
|
948
|
+
// parseExpr's own error — including the CVE-2023-25345 guard
|
|
949
|
+
// message — instead of masking it.
|
|
950
|
+
if (hasTopTernary) { throw e; }
|
|
877
951
|
return legacyFallback();
|
|
878
952
|
}
|
|
879
953
|
},
|
package/lib/tokentypes.js
CHANGED